4 Copyright (C) 1996 Free Software Foundation, Inc.
6 Author: Ovidiu Predescu <ovidiu@apache.org>
7 Helge Hess <helge.hess@opengroupware.org>
11 This file is part of the GNUstep Database Library.
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.
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.
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.
31 #import "EOSQLQualifier.h"
33 #import "EOAttribute.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"
43 #if LIB_FOUNDATION_LIBRARY
44 # include <extensions/DefaultScannerHandler.h>
45 # include <extensions/PrintfFormatScanner.h>
47 # include "DefaultScannerHandler.h"
48 # include "PrintfFormatScanner.h"
51 @interface EOQualifierJoinHolder : NSObject
56 + (id)valueForSource:(id)source destination:(id)destination;
57 - (NSString*)expressionValueForContext:(EOSQLExpression*)context;
62 @implementation EOQualifierJoinHolder
64 static Class AttributeClass = Nil;
65 static EONull *null = nil;
68 AttributeClass = [EOAttribute class];
69 null = [[NSNull null] retain];
72 + (id)valueForSource:(id)_source destination:(id)_destination {
73 EOQualifierJoinHolder *value;
75 value = [[[self alloc] init] autorelease];
76 value->source = [_source retain];
77 value->destination = [_destination retain];
81 - (NSString *)expressionValueForContext:(EOSQLExpression *)context {
82 NSMutableString *result = nil;
83 EOAdaptor *adaptor = nil;
84 NSString *formattedLeft = nil;
85 NSString *formattedRight = nil;
88 adaptor = [context adaptor];
90 if ([source isKindOfClass:AttributeClass]) {
91 formattedLeft = [(EOAttribute *)source expressionValueForContext:context];
94 NSAssert([destination isKindOfClass:AttributeClass],
95 @"either one of source or destination should be EOAttribute");
96 if ([source isEqual:null] || (source == nil))
99 formattedLeft = [adaptor formatValue:source?source:null
100 forAttribute:destination];
103 if ([destination isKindOfClass:AttributeClass]) {
104 NSString *tmp = formattedLeft;
107 [(EOAttribute *)destination expressionValueForContext:context];
108 formattedRight = tmp;
111 NSAssert([source isKindOfClass:AttributeClass],
112 @"either one of source or destination should be EOAttribute");
114 if ([destination isEqual:null] || (destination == nil))
117 formattedRight = [adaptor formatValue:destination?destination:null
118 forAttribute:source];
121 result = [NSMutableString stringWithCapacity:64];
122 [result appendString:formattedLeft];
123 [result appendString:checkNull ? @" IS " : @"="];
124 [result appendString:formattedRight];
132 return self->destination;
135 @end /* EOQualifierJoinHolder */
138 @implementation EOSQLQualifier
140 + (EOSQLQualifier*)qualifierForRow:(NSDictionary*)row
141 entity:(EOEntity*)_entity
143 EOSQLQualifier *qualifier = nil;
144 NSEnumerator *enumerator = nil;
145 NSString *attributeName = nil;
146 EOAttribute *attribute = nil;
150 enumerator = [row keyEnumerator];
151 qualifier = [[[EOSQLQualifier alloc] init] autorelease];
153 while ((attributeName = [enumerator nextObject])) {
154 attribute = [_entity attributeNamed:attributeName];
155 value = [row objectForKey:attributeName];
157 if ((value == nil) || (attribute == nil))
158 /* return nil when is unable to build a qualifier for all keys
166 [qualifier->content addObject:@" AND "];
168 [qualifier->content addObject:
169 [EOQualifierJoinHolder valueForSource:attribute destination:value]];
172 qualifier->entity = RETAIN(_entity);
173 [qualifier _computeRelationshipPaths];
178 + (EOSQLQualifier*)qualifierForPrimaryKey:(NSDictionary*)dictionary
179 entity:(EOEntity*)_entity
181 NSDictionary *pkey = nil;
183 pkey = [_entity primaryKeyForRow:dictionary];
184 /* return nil when is unable to build a complete qualifier
185 for all primary key attributes
187 return pkey ? [self qualifierForRow:pkey entity:_entity] : nil;
190 + (EOSQLQualifier*)qualifierForRow:(NSDictionary*)row
191 relationship:(EORelationship*)relationship
193 NSArray *componentRelationships = nil;
194 EOSQLQualifier *qualifier = nil;
195 NSArray *sourceAttributes = nil;
196 id tmpRelationship = nil;
197 EOAttribute *sourceAttribute = nil;
198 EOAttribute *destinationAttribute = nil;
203 componentRelationships = [relationship componentRelationships];
204 tmpRelationship = relationship;
205 qualifier = [[[EOSQLQualifier alloc] init] autorelease];
207 /* Make a qualifier string in the following manner. If the relationship is
208 not flattened we must join using the join operator the values from `row'
209 and the foreign keys taken from the destination entity of relatioship.
210 If the relationship is flattend we must append then joins between the
211 components of relationship. */
213 if (componentRelationships) {
214 tmpRelationship = [componentRelationships objectAtIndex:0];
217 [NSArray arrayWithObject:[tmpRelationship sourceAttribute]];
221 [NSArray arrayWithObject:[relationship sourceAttribute]];
224 sourceAttribute = [tmpRelationship sourceAttribute];
225 value = [row objectForKey:[sourceAttribute name]];
227 /* Returns nil if `row' does not contain all the values needed to
228 create a complete qualifier
232 destinationAttribute = [tmpRelationship destinationAttribute];
233 [qualifier->content addObject:
234 [EOQualifierJoinHolder valueForSource:destinationAttribute
237 if (componentRelationships) {
238 EOEntity *tempEntity = [tmpRelationship destinationEntity];
240 /* The relationship is flattened. Iterate over the components and
241 add joins that `link' the components between them.
243 count2 = [componentRelationships count];
244 for (j = 1; j < count2; j++) {
245 relationship = [componentRelationships objectAtIndex:j];
247 if ([relationship sourceAttribute]) {
248 [qualifier->content addObject:@" AND "];
249 [qualifier->content addObject:
250 [EOQualifierJoinHolder valueForSource:
251 [relationship sourceAttribute]
253 [relationship destinationAttribute]]];
257 /* Here we make a hack because later we need to use this qualifier in
258 a SELECT expression in which the qualifier's entity should be the
259 final destination entity of the flattened relationship. In addition
260 we need in the FROM clause all the entities corresponding to the
261 components of the relationship to be able to insert the joins
262 between the values given in row and the final attributes from the
263 destination entity of the last component of relationship. */
264 ASSIGN(qualifier->entity, tempEntity);
265 [qualifier _computeRelationshipPaths];
266 ASSIGN(qualifier->entity, [relationship destinationEntity]);
270 ASSIGN(qualifier->entity, [relationship destinationEntity]);
275 + (EOSQLQualifier *)qualifierForObject:sourceObject
276 relationship:(EORelationship *)relationship
278 return [self qualifierForRow:
279 [sourceObject valueForKey:[[relationship sourceAttribute] name]]
280 relationship:relationship];
284 NSZone *z = [self zone];
286 RELEASE(self->content); self->content = nil;
287 RELEASE(self->relationshipPaths); self->relationshipPaths = nil;
288 RELEASE(self->additionalEntities); self->additionalEntities = nil;
290 self->content = [[EOExpressionArray allocWithZone:z] init];
291 self->relationshipPaths = [[NSMutableSet allocWithZone:z] init];
292 self->additionalEntities = [[NSMutableSet allocWithZone:z] init];
296 - (id)initWithEntity:(EOEntity *)_entity
297 qualifierFormat:(NSString *)_qualifierFormat
298 argumentsArray:(NSArray *)_args
300 PrintfFormatScanner *formatScanner = nil;
301 EOQualifierEnumScannerHandler *scannerHandler = nil;
302 NSString *qualifierString = nil;
303 NSMutableArray *myRelationshipPaths = nil;
304 NSEnumerator *args = nil;
306 myRelationshipPaths = [[NSMutableArray allocWithZone:[self zone]] init];
309 ASSIGN(self->entity, _entity);
311 if (_qualifierFormat == nil)
314 formatScanner = [[PrintfFormatScanner alloc] init];
315 scannerHandler = [[EOQualifierEnumScannerHandler alloc] init];
316 [formatScanner setAllowOnlySpecifier:YES];
318 args = [_args objectEnumerator];
319 [scannerHandler setEntity:_entity];
321 [formatScanner setFormatScannerHandler:scannerHandler];
323 Note: This is an ugly hack. Arguments is supposed to be a va_args
324 structure, but an NSArray is passed in.
325 It works because the value is casted to -parseFormatString:context:
326 which gives control to the scannerHandler which casts the va_args
327 back to an array (the EOQualifierEnumScannerHandler does that).
328 Works on ix86, but *NOT* on iSeries or zServer !!
330 #if defined(__s390__)
332 [formatScanner performSelector:@selector(stringWithFormat:arguments:)
333 withObject:_qualifierFormat
336 // TODO: args is an NSArray, PrintfFormatScanner expects a va_list?
337 // I think that this is OK because we use EOQualifierEnumScannerHandler
339 [formatScanner stringWithFormat:_qualifierFormat
340 arguments:(void *)args];
343 [formatScanner release]; formatScanner = nil;
344 [scannerHandler release]; scannerHandler = nil;
346 [self->content release]; self->content = nil;
348 [[EOExpressionArray parseExpression:qualifierString
350 replacePropertyReferences:YES
351 relationshipPaths:myRelationshipPaths]
353 [self _computeRelationshipPaths:myRelationshipPaths];
354 [myRelationshipPaths release]; myRelationshipPaths = nil;
358 - (id)initWithEntity:(EOEntity*)_entity
359 qualifierFormat:(NSString *)qualifierFormat, ...
362 id formatScanner = nil;
363 id scannerHandler = nil;
364 NSString *qualifierString = nil;
365 NSMutableArray *myRelationshipPaths = nil;
367 if ((self = [self init]) == nil)
370 myRelationshipPaths = [[NSMutableArray alloc] init];
371 ASSIGN(self->entity, _entity);
373 if (qualifierFormat == nil) {
377 formatScanner = [[PrintfFormatScanner alloc] init];
378 scannerHandler = [[EOQualifierScannerHandler alloc] init];
379 [formatScanner setAllowOnlySpecifier:YES];
381 va_start(ap, qualifierFormat);
382 [scannerHandler setEntity:_entity];
383 [formatScanner setFormatScannerHandler:scannerHandler];
384 qualifierString = [formatScanner stringWithFormat:qualifierFormat
388 [formatScanner release];
389 [scannerHandler release];
391 [self->content release]; self->content = nil;
393 [[EOExpressionArray parseExpression:qualifierString
395 replacePropertyReferences:YES
396 relationshipPaths:myRelationshipPaths] retain];
397 [self _computeRelationshipPaths:myRelationshipPaths];
398 [myRelationshipPaths release]; myRelationshipPaths = nil;
402 - (void)_computeRelationshipPaths {
403 [self _computeRelationshipPaths:nil];
407 handle_attribute(EOSQLQualifier *self, id object, id _relationshipPaths)
409 [self->additionalEntities addObject:[object entity]];
412 - (void)_computeRelationshipPaths:(NSArray *)_relationshipPaths {
415 [relationshipPaths removeAllObjects];
417 if (_relationshipPaths) {
418 NSEnumerator *pathEnum = [_relationshipPaths objectEnumerator];
419 NSArray *relPath = nil;
421 while ((relPath = [pathEnum nextObject])) {
422 NSEnumerator *relEnum = nil;
423 EORelationship *rel = nil;
425 relEnum = [relPath objectEnumerator];
427 while ((rel = [relEnum nextObject])) {
428 [additionalEntities addObject:[rel destinationEntity]];
431 [relationshipPaths addObjectsFromArray:_relationshipPaths];
433 for (i = 0, count = [content count]; i < count; i++) {
434 id object = [content objectAtIndex:i];
436 /* The objects from content can only be NSString, values or
438 if ([object isKindOfClass:[EOAttribute class]]) {
439 handle_attribute (self, object, _relationshipPaths);
441 else if ([object isKindOfClass:[EOQualifierJoinHolder class]]) {
443 id destination = nil;
445 source = [object source];
446 destination = [object destination];
448 if ([source isKindOfClass:[EOAttribute class]])
449 handle_attribute (self, source, _relationshipPaths);
450 if ([destination isKindOfClass:[EOAttribute class]])
451 handle_attribute (self, destination, _relationshipPaths);
453 else if ([object isKindOfClass:[EORelationship class]]) {
454 [[[InvalidPropertyException alloc]
455 initWithFormat:@"cannot refer a EORelat"
456 @"ionship in a EOSQLQualifier: '%@'",
457 [(EORelationship*)object name]] raise];
463 RELEASE(self->relationshipPaths);
464 RELEASE(self->additionalEntities);
466 RELEASE(self->entity);
467 RELEASE(self->content);
472 return [self copyWithZone:NSDefaultMallocZone()];
475 - (id)copyWithZone:(NSZone*)zone {
476 EOSQLQualifier* copy = nil;
478 copy = [[self->isa allocWithZone:zone] init];
479 copy->entity = RETAIN(self->entity);
480 copy->content = [self->content mutableCopyWithZone:zone];
481 copy->relationshipPaths = [self->relationshipPaths mutableCopyWithZone:zone];
482 copy->usesDistinct = self->usesDistinct;
487 [self->content insertObject:@"NOT (" atIndex:0];
488 [self->content addObject:@")"];
491 - (void)conjoinWithQualifier:(EOSQLQualifier*)qualifier {
492 if (![qualifier isKindOfClass:[EOSQLQualifier class]]) {
493 [NSException raise:NSInvalidArgumentException
494 format:@"argument of conjoinWithQualifier: method must "
495 @"be EOSQLQualifier"];
498 if (self->entity != qualifier->entity) {
499 [NSException raise:NSInvalidArgumentException
500 format:@"qualifier argument of conjoinWithQualifier: "
501 @"must have the same entity as receiver"];
504 [self->content insertObject:@"(" atIndex:0];
505 [self->content addObject:@") AND ("];
506 [self->content addObjectsFromExpressionArray:qualifier->content];
507 [self->content addObject:@")"];
508 [self->relationshipPaths unionSet:qualifier->relationshipPaths];
511 - (void)disjoinWithQualifier:(EOSQLQualifier*)qualifier {
512 if (![qualifier isKindOfClass:[EOSQLQualifier class]]) {
513 [NSException raise:NSInvalidArgumentException
514 format:@"argument of disjoinWithQualifier: method must "
515 @"be EOSQLQualifier"];
518 if (self->entity != qualifier->entity) {
519 [NSException raise:NSInvalidArgumentException
520 format:@"qualifier argument of disjoinWithQualifier: "
521 @"must have the same entity as receiver"];
524 [self->content insertObject:@"(" atIndex:0];
525 [self->content addObject:@") OR ("];
526 [self->content addObjectsFromExpressionArray:qualifier->content];
527 [self->content addObject:@")"];
528 [self->relationshipPaths unionSet:qualifier->relationshipPaths];
531 - (EOEntity*)entity {
535 return (self->entity == nil) ? YES : NO;
537 - (void)setUsesDistinct:(BOOL)flag {
538 self->usesDistinct = flag;
540 - (BOOL)usesDistinct {
541 return self->usesDistinct;
543 - (NSMutableSet*)relationshipPaths {
544 return self->relationshipPaths;
546 - (NSMutableSet*)additionalEntities {
547 return self->additionalEntities;
550 - (NSString*)expressionValueForContext:(id<EOExpressionContext>)ctx {
551 return [self->content expressionValueForContext:ctx];
554 - (EOSQLQualifier *)sqlQualifierForEntity:(EOEntity *)_entity {
555 NSAssert3(self->entity == _entity,
556 @"passed invalid entity to %@ (contains %@, got %@)",
557 self, self->entity, _entity);
558 return (EOSQLQualifier *)self;
563 - (NSString *)description {
566 ms = [NSMutableString stringWithCapacity:128];
567 [ms appendFormat:@"<0x%08X[%@]:\n", self, NSStringFromClass([self class])];
569 [ms appendFormat:@" entity=%@", [self->entity name]];
572 [ms appendFormat:@" content=%@", self->content];
574 if (self->usesDistinct)
575 [ms appendString:@" distinct"];
577 [ms appendString:@">"];
581 @end /* EOSQLQualifier */