]> err.no Git - sope/blob - sope-gdl1/GDLAccess/EOSQLQualifier.m
fixed bug in GDL adaptor lookup
[sope] / sope-gdl1 / GDLAccess / EOSQLQualifier.m
1 /* 
2    EOSQLQualifier.m
3
4    Copyright (C) 1996 Free Software Foundation, Inc.
5
6    Author: Ovidiu Predescu <ovidiu@apache.org>
7            Helge Hess <helge.hess@opengroupware.org>
8    Date:   September 1996
9            November  1999
10
11    This file is part of the GNUstep Database Library.
12
13    This library is free software; you can redistribute it and/or
14    modify it under the terms of the GNU Library General Public
15    License as published by the Free Software Foundation; either
16    version 2 of the License, or (at your option) any later version.
17
18    This library is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21    Library General Public License for more details.
22
23    You should have received a copy of the GNU Library General Public
24    License along with this library; see the file COPYING.LIB.
25    If not, write to the Free Software Foundation,
26    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28
29 #include <stdio.h>
30 #import "common.h"
31 #import "EOSQLQualifier.h"
32 #import "EOAdaptor.h"
33 #import "EOAttribute.h"
34 #import "EOEntity.h"
35 #import "EOExpressionArray.h"
36 #import "EOFExceptions.h"
37 #import "EORelationship.h"
38 #import "EOSQLExpression.h"
39 #include <EOControl/EOKeyValueCoding.h>
40 #include <EOControl/EONull.h>
41 #import "EOQualifierScanner.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 @interface EOQualifierJoinHolder : NSObject
52 {
53   id source;
54   id destination;
55 }
56 + (id)valueForSource:(id)source destination:(id)destination;
57 - (NSString*)expressionValueForContext:(EOSQLExpression*)context;
58 - (id)source;
59 - (id)destination;
60 @end
61
62 @implementation EOQualifierJoinHolder
63
64 static Class  AttributeClass = Nil;
65 static EONull *null          = nil;
66
67 + (void)initialize {
68   AttributeClass = [EOAttribute class];
69   null           = [[NSNull null] retain];
70 }
71
72 + (id)valueForSource:(id)_source destination:(id)_destination {
73   EOQualifierJoinHolder *value;
74   
75   value              = [[[self alloc] init] autorelease];
76   value->source      = [_source      retain];
77   value->destination = [_destination retain];
78   return value;
79 }
80
81 - (NSString *)expressionValueForContext:(EOSQLExpression *)context {
82   NSMutableString *result         = nil;
83   EOAdaptor       *adaptor        = nil;
84   NSString        *formattedLeft  = nil;
85   NSString        *formattedRight = nil;
86   BOOL            checkNull       = NO;
87
88   adaptor = [context adaptor];
89   
90   if ([source isKindOfClass:AttributeClass]) {
91     formattedLeft = [source expressionValueForContext:context];
92   }
93   else {
94     NSAssert([destination isKindOfClass:AttributeClass],
95              @"either one of source or destination should be EOAttribute");
96     if ([source isEqual:null] || (source == nil))
97       checkNull = YES;
98     
99     formattedLeft = [adaptor formatValue:source?source:null
100                              forAttribute:destination];
101   }
102
103   if ([destination isKindOfClass:AttributeClass]) {
104     NSString *tmp = formattedLeft;
105     
106     formattedLeft  = [destination expressionValueForContext:context];
107     formattedRight = tmp;
108   }
109   else {
110     NSAssert([source isKindOfClass:AttributeClass],
111              @"either one of source or destination should be EOAttribute");
112     
113     if ([destination isEqual:null] || (destination == nil))
114       checkNull = YES;
115     
116     formattedRight = [adaptor formatValue:destination?destination:null
117                               forAttribute:source];
118   }
119
120   result = [NSMutableString stringWithCapacity:64];
121   [result appendString:formattedLeft];
122   [result appendString:checkNull ? @" IS " : @"="];
123   [result appendString:formattedRight];
124   return result;
125 }
126
127 - (id)source {
128     return self->source;
129 }
130 - (id)destination {
131     return self->destination;
132 }
133
134 @end /* EOQualifierJoinHolder */
135
136
137 @implementation EOSQLQualifier
138
139 + (EOSQLQualifier*)qualifierForRow:(NSDictionary*)row
140   entity:(EOEntity*)_entity
141 {
142   EOSQLQualifier  *qualifier     = nil;
143   NSEnumerator    *enumerator    = nil;
144   NSString        *attributeName = nil;
145   EOAttribute     *attribute     = nil;
146   id              value          = nil;
147   BOOL            first          = YES;
148
149   enumerator = [row keyEnumerator];    
150   qualifier  = [[[EOSQLQualifier alloc] init] autorelease];
151
152   while ((attributeName = [enumerator nextObject])) {
153     attribute = [_entity attributeNamed:attributeName];
154     value = [row objectForKey:attributeName];
155
156     if ((value == nil) || (attribute == nil))
157       /* return nil when is unable to build a qualifier for all keys
158          in the given row
159       */
160       return nil;
161
162     if (first)
163       first = NO;
164     else
165       [qualifier->content addObject:@" AND "];
166
167     [qualifier->content addObject:
168               [EOQualifierJoinHolder valueForSource:attribute destination:value]];
169   }
170
171   qualifier->entity = RETAIN(_entity);
172   [qualifier _computeRelationshipPaths];
173
174   return qualifier;
175 }
176
177 + (EOSQLQualifier*)qualifierForPrimaryKey:(NSDictionary*)dictionary
178   entity:(EOEntity*)_entity
179 {
180   NSDictionary *pkey = nil;
181
182   pkey = [_entity primaryKeyForRow:dictionary];
183     /* return nil when is unable to build a complete qualifier
184        for all primary key attributes
185     */
186   return pkey ? [self qualifierForRow:pkey entity:_entity] : nil;
187 }
188
189 + (EOSQLQualifier*)qualifierForRow:(NSDictionary*)row 
190   relationship:(EORelationship*)relationship
191 {
192   NSArray        *componentRelationships = nil;
193   EOSQLQualifier *qualifier              = nil;
194   NSArray        *sourceAttributes       = nil;
195   id             tmpRelationship         = nil;
196   EOAttribute    *sourceAttribute        = nil;
197   EOAttribute    *destinationAttribute   = nil;
198   id             value                   = nil;
199   int            j                       = 0;
200   int            count2                  = 0;
201
202   componentRelationships = [relationship componentRelationships];
203   tmpRelationship        = relationship;
204   qualifier              = [[[EOSQLQualifier alloc] init] autorelease];
205
206   /* Make a qualifier string in the following manner. If the relationship is
207      not flattened we must join using the join operator the values from `row'
208      and the foreign keys taken from the destination entity of relatioship.
209      If the relationship is flattend we must append then joins between the
210      components of relationship. */
211
212   if (componentRelationships) {
213     tmpRelationship = [componentRelationships objectAtIndex:0];
214         
215     sourceAttributes =
216       [NSArray arrayWithObject:[tmpRelationship sourceAttribute]];
217   }
218   else {
219     sourceAttributes =
220       [NSArray arrayWithObject:[relationship sourceAttribute]];
221   }
222
223   sourceAttribute = [tmpRelationship sourceAttribute];
224   value           = [row objectForKey:[sourceAttribute name]];
225   if (value == nil)
226     /* Returns nil if `row' does not contain all the values needed to 
227        create a complete qualifier
228     */
229     return nil;
230
231   destinationAttribute = [tmpRelationship destinationAttribute];
232   [qualifier->content addObject:
233             [EOQualifierJoinHolder valueForSource:destinationAttribute
234                                    destination:value]];
235
236   if (componentRelationships) {
237     EOEntity *tempEntity = [tmpRelationship destinationEntity];
238
239     /* The relationship is flattened. Iterate over the components and 
240        add joins that `link' the components between them.
241     */
242     count2 = [componentRelationships count];
243     for (j = 1; j < count2; j++) {
244       relationship = [componentRelationships objectAtIndex:j];
245             
246       if ([relationship sourceAttribute]) {
247         [qualifier->content addObject:@" AND "];
248         [qualifier->content addObject:
249                   [EOQualifierJoinHolder valueForSource:
250                                          [relationship sourceAttribute]
251                                          destination:
252                                          [relationship destinationAttribute]]];
253       }
254     }
255
256     /* Here we make a hack because later we need to use this qualifier in
257        a SELECT expression in which the qualifier's entity should be the
258        final destination entity of the flattened relationship. In addition
259        we need in the FROM clause all the entities corresponding to the
260        components of the relationship to be able to insert the joins
261        between the values given in row and the final attributes from the
262        destination entity of the last component of relationship. */
263     ASSIGN(qualifier->entity, tempEntity);
264     [qualifier _computeRelationshipPaths];
265     ASSIGN(qualifier->entity, [relationship destinationEntity]);
266     return qualifier;
267   }
268   else {
269     ASSIGN(qualifier->entity, [relationship destinationEntity]);
270     return qualifier;
271   }
272 }
273
274 + (EOSQLQualifier *)qualifierForObject:sourceObject 
275   relationship:(EORelationship *)relationship
276 {
277   return [self qualifierForRow:
278                  [sourceObject valueForKey:[[relationship sourceAttribute] name]]
279                relationship:relationship];
280 }
281
282 - (id)init {
283   NSZone *z = [self zone];
284     
285   RELEASE(self->content);            self->content            = nil;
286   RELEASE(self->relationshipPaths);  self->relationshipPaths  = nil;
287   RELEASE(self->additionalEntities); self->additionalEntities = nil;
288
289   self->content            = [[EOExpressionArray allocWithZone:z] init];
290   self->relationshipPaths  = [[NSMutableSet allocWithZone:z] init];
291   self->additionalEntities = [[NSMutableSet allocWithZone:z] init];
292   return self;
293 }
294
295 - (id)initWithEntity:(EOEntity *)_entity 
296   qualifierFormat:(NSString *)_qualifierFormat
297   argumentsArray:(NSArray *)_args
298 {
299   PrintfFormatScanner           *formatScanner       = nil;
300   EOQualifierEnumScannerHandler *scannerHandler      = nil;
301   NSString                      *qualifierString     = nil;
302   NSMutableArray                *myRelationshipPaths = nil;
303   NSEnumerator                  *args                = nil;
304
305   myRelationshipPaths = [[NSMutableArray allocWithZone:[self zone]] init];
306     
307   [self init];
308   ASSIGN(self->entity, _entity);
309
310   if (_qualifierFormat == nil)
311     return self;
312
313   formatScanner  = [[PrintfFormatScanner alloc] init];
314   scannerHandler = [[EOQualifierEnumScannerHandler alloc] init];
315   [formatScanner setAllowOnlySpecifier:YES];
316
317   args = [_args objectEnumerator];
318   [scannerHandler setEntity:_entity];
319     
320   [formatScanner setFormatScannerHandler:scannerHandler];
321   /*
322     Note: This is an ugly hack. Arguments is supposed to be a va_args
323           structure, but an NSArray is passed in.
324           It works because the value is casted to -parseFormatString:context:
325           which gives control to the scannerHandler which casts the va_args
326           back to an array (the EOQualifierEnumScannerHandler does that).
327           Works on ix86, but *NOT* on iSeries or zServer !!
328   */
329 #if defined(__s390__)
330   qualifierString =
331     [formatScanner performSelector:@selector(stringWithFormat:arguments:)
332                    withObject:_qualifierFormat
333                    withObject:args];
334 #else
335   // TODO: args is an NSArray, PrintfFormatScanner expects a va_list?
336   //       I think that this is OK because we use EOQualifierEnumScannerHandler
337   qualifierString = 
338     [formatScanner stringWithFormat:_qualifierFormat
339                    arguments:(void *)args];
340 #endif
341
342   [formatScanner  release]; formatScanner  = nil;
343   [scannerHandler release]; scannerHandler = nil;
344   
345   [self->content release]; self->content = nil;
346   self->content =
347          [[EOExpressionArray parseExpression:qualifierString
348                             entity:entity
349                             replacePropertyReferences:YES
350                             relationshipPaths:myRelationshipPaths]
351            retain];
352   [self _computeRelationshipPaths:myRelationshipPaths];
353   [myRelationshipPaths release]; myRelationshipPaths = nil;
354   return self;
355 }
356
357 - (id)initWithEntity:(EOEntity*)_entity 
358   qualifierFormat:(NSString *)qualifierFormat, ...
359 {
360   va_list        ap;
361   id             formatScanner        = nil;
362   id             scannerHandler       = nil;
363   NSString       *qualifierString     = nil;
364   NSMutableArray *myRelationshipPaths = nil;
365
366   if ((self = [self init]) == nil)
367     return nil;
368   
369   myRelationshipPaths = [[NSMutableArray alloc] init];
370   ASSIGN(self->entity, _entity);
371   
372   if (qualifierFormat == nil) {
373     return self;
374   }
375   
376   formatScanner  = [[PrintfFormatScanner alloc] init];
377   scannerHandler = [[EOQualifierScannerHandler alloc] init];
378   [formatScanner setAllowOnlySpecifier:YES];
379   
380   va_start(ap, qualifierFormat);
381   [scannerHandler setEntity:_entity];
382   [formatScanner setFormatScannerHandler:scannerHandler];
383   qualifierString = [formatScanner stringWithFormat:qualifierFormat
384                                    arguments:ap];
385   va_end(ap);
386
387   [formatScanner  release];
388   [scannerHandler release];
389
390   [self->content release]; self->content = nil;
391   self->content =
392          [[EOExpressionArray parseExpression:qualifierString
393                             entity:entity
394                             replacePropertyReferences:YES
395                             relationshipPaths:myRelationshipPaths] retain];
396   [self _computeRelationshipPaths:myRelationshipPaths];
397   [myRelationshipPaths release]; myRelationshipPaths = nil;
398   return self;
399 }
400
401 - (void)_computeRelationshipPaths {
402   [self _computeRelationshipPaths:nil];
403 }
404
405 static void
406 handle_attribute(EOSQLQualifier *self, id object, id _relationshipPaths)
407 {
408   if ([object isFlattened]) {
409     id      definitionArray = nil;
410     id      relsArray       = nil;    
411     NSRange range;
412
413     definitionArray = [object definitionArray];
414     range           = NSMakeRange(0, [definitionArray count] - 1);
415     relsArray       = [definitionArray subarrayWithRange:range];
416
417     [self->relationshipPaths addObject:relsArray];
418     [self->additionalEntities addObjectsFromArray:relsArray];
419   }
420   else {
421     [self->additionalEntities addObject:[object entity]];
422   }
423 }
424
425 - (void)_computeRelationshipPaths:(NSArray *)_relationshipPaths {
426   int i, count;
427
428   [relationshipPaths removeAllObjects];
429
430   if (_relationshipPaths) {
431     NSEnumerator *pathEnum = [_relationshipPaths objectEnumerator];
432     NSArray      *relPath  = nil;
433         
434     while ((relPath = [pathEnum nextObject])) {
435       NSEnumerator   *relEnum = nil;
436       EORelationship *rel     = nil;
437
438       relEnum = [relPath objectEnumerator];
439
440       while ((rel = [relEnum nextObject])) {
441         [additionalEntities addObject:[rel destinationEntity]];
442       }
443     }
444     [relationshipPaths addObjectsFromArray:_relationshipPaths];
445   }
446   for (i = 0, count = [content count]; i < count; i++) {
447     id object = [content objectAtIndex:i];
448
449     /* The objects from content can only be NSString, values or
450        EOAttribute. */
451     if ([object isKindOfClass:[EOAttribute class]]) {
452       handle_attribute (self, object, _relationshipPaths);
453     }
454     else if ([object isKindOfClass:[EOQualifierJoinHolder class]]) {
455       id source      = nil;
456       id destination = nil;
457
458       source      = [object source];
459       destination = [object destination];
460
461       if ([source isKindOfClass:[EOAttribute class]])
462         handle_attribute (self, source, _relationshipPaths);
463       if ([destination isKindOfClass:[EOAttribute class]])
464         handle_attribute (self, destination, _relationshipPaths);
465     }
466     else if ([object isKindOfClass:[EORelationship class]]) {
467       [[[InvalidPropertyException alloc]
468                                        initWithFormat:@"cannot refer a EORelat"
469                                        @"ionship in a EOSQLQualifier: '%@'",
470                                        [(EORelationship*)object name]] raise];
471     }
472   }
473 }
474
475 - (void)dealloc {
476   RELEASE(self->relationshipPaths);
477   RELEASE(self->additionalEntities);
478   
479   RELEASE(self->entity);
480   RELEASE(self->content);
481   [super dealloc];
482 }
483
484 - (id)copy {
485   return [self copyWithZone:NSDefaultMallocZone()];
486 }
487
488 - (id)copyWithZone:(NSZone*)zone {
489   EOSQLQualifier* copy = nil;
490
491   copy                    = [[self->isa allocWithZone:zone] init];
492   copy->entity            = RETAIN(self->entity);
493   copy->content           = [self->content           mutableCopyWithZone:zone];
494   copy->relationshipPaths = [self->relationshipPaths mutableCopyWithZone:zone];
495   copy->usesDistinct      = self->usesDistinct;
496   return copy;
497 }
498
499 - (void)negate {
500   [self->content insertObject:@"NOT (" atIndex:0];
501   [self->content addObject:@")"];
502 }
503
504 - (void)conjoinWithQualifier:(EOSQLQualifier*)qualifier {
505   if (![qualifier isKindOfClass:[EOSQLQualifier class]]) {
506     [NSException raise:NSInvalidArgumentException
507                  format:@"argument of conjoinWithQualifier: method must "
508                      @"be EOSQLQualifier"];
509   }
510
511   if (self->entity != qualifier->entity) {
512     [NSException raise:NSInvalidArgumentException
513                  format:@"qualifier argument of conjoinWithQualifier: "
514                              @"must have the same entity as receiver"];
515   }
516
517   [self->content insertObject:@"(" atIndex:0];
518   [self->content addObject:@") AND ("];
519   [self->content addObjectsFromExpressionArray:qualifier->content];
520   [self->content addObject:@")"];
521   [self->relationshipPaths unionSet:qualifier->relationshipPaths];
522 }
523
524 - (void)disjoinWithQualifier:(EOSQLQualifier*)qualifier {
525   if (![qualifier isKindOfClass:[EOSQLQualifier class]]) {
526     [NSException raise:NSInvalidArgumentException
527                  format:@"argument of disjoinWithQualifier: method must "
528                     @"be EOSQLQualifier"];
529   }
530
531   if (self->entity != qualifier->entity) {
532     [NSException raise:NSInvalidArgumentException
533                  format:@"qualifier argument of disjoinWithQualifier: "
534                     @"must have the same entity as receiver"];
535   }
536
537   [self->content insertObject:@"(" atIndex:0];
538   [self->content addObject:@") OR ("];
539   [self->content addObjectsFromExpressionArray:qualifier->content];
540   [self->content addObject:@")"];
541   [self->relationshipPaths unionSet:qualifier->relationshipPaths];
542 }
543
544 - (EOEntity*)entity {
545   return self->entity;
546 }
547 - (BOOL)isEmpty {
548   return (self->entity == nil) ? YES : NO;
549 }
550 - (void)setUsesDistinct:(BOOL)flag {
551   self->usesDistinct = flag;
552 }
553 - (BOOL)usesDistinct {
554   return self->usesDistinct;
555 }
556 - (NSMutableSet*)relationshipPaths {
557   return self->relationshipPaths;
558 }
559 - (NSMutableSet*)additionalEntities {
560   return self->additionalEntities;
561 }
562
563 - (NSString*)expressionValueForContext:(id<EOExpressionContext>)ctx {
564     return [self->content expressionValueForContext:ctx];
565 }
566
567 - (EOSQLQualifier *)sqlQualifierForEntity:(EOEntity *)_entity {
568   NSAssert3(self->entity == _entity,
569             @"passed invalid entity to %@ (contains %@, got %@)",
570             self, self->entity, _entity);
571   return (EOSQLQualifier *)self;
572 }
573
574 /* description */
575
576 - (NSString *)description {
577   NSMutableString *ms;
578
579   ms = [NSMutableString stringWithCapacity:128];
580   [ms appendFormat:@"<0x%08X[%@]:\n", self, NSStringFromClass([self class])];
581
582   [ms appendFormat:@" entity=%@", [self->entity name]];
583   
584   if (self->content)
585     [ms appendFormat:@" content=%@", self->content];
586   
587   if (self->usesDistinct)
588     [ms appendString:@" distinct"];
589   
590   [ms appendString:@">"];
591   return ms;
592 }
593
594 @end /* EOSQLQualifier */