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 = [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;
106 formattedLeft = [destination expressionValueForContext:context];
107 formattedRight = tmp;
110 NSAssert([source isKindOfClass:AttributeClass],
111 @"either one of source or destination should be EOAttribute");
113 if ([destination isEqual:null] || (destination == nil))
116 formattedRight = [adaptor formatValue:destination?destination:null
117 forAttribute:source];
120 result = [NSMutableString stringWithCapacity:64];
121 [result appendString:formattedLeft];
122 [result appendString:checkNull ? @" IS " : @"="];
123 [result appendString:formattedRight];
131 return self->destination;
134 @end /* EOQualifierJoinHolder */
137 @implementation EOSQLQualifier
139 + (EOSQLQualifier*)qualifierForRow:(NSDictionary*)row
140 entity:(EOEntity*)_entity
142 EOSQLQualifier *qualifier = nil;
143 NSEnumerator *enumerator = nil;
144 NSString *attributeName = nil;
145 EOAttribute *attribute = nil;
149 enumerator = [row keyEnumerator];
150 qualifier = [[[EOSQLQualifier alloc] init] autorelease];
152 while ((attributeName = [enumerator nextObject])) {
153 attribute = [_entity attributeNamed:attributeName];
154 value = [row objectForKey:attributeName];
156 if ((value == nil) || (attribute == nil))
157 /* return nil when is unable to build a qualifier for all keys
165 [qualifier->content addObject:@" AND "];
167 [qualifier->content addObject:
168 [EOQualifierJoinHolder valueForSource:attribute destination:value]];
171 qualifier->entity = RETAIN(_entity);
172 [qualifier _computeRelationshipPaths];
177 + (EOSQLQualifier*)qualifierForPrimaryKey:(NSDictionary*)dictionary
178 entity:(EOEntity*)_entity
180 NSDictionary *pkey = nil;
182 pkey = [_entity primaryKeyForRow:dictionary];
183 /* return nil when is unable to build a complete qualifier
184 for all primary key attributes
186 return pkey ? [self qualifierForRow:pkey entity:_entity] : nil;
189 + (EOSQLQualifier*)qualifierForRow:(NSDictionary*)row
190 relationship:(EORelationship*)relationship
192 NSArray *componentRelationships = nil;
193 EOSQLQualifier *qualifier = nil;
194 NSArray *sourceAttributes = nil;
195 id tmpRelationship = nil;
196 EOAttribute *sourceAttribute = nil;
197 EOAttribute *destinationAttribute = nil;
202 componentRelationships = [relationship componentRelationships];
203 tmpRelationship = relationship;
204 qualifier = [[[EOSQLQualifier alloc] init] autorelease];
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. */
212 if (componentRelationships) {
213 tmpRelationship = [componentRelationships objectAtIndex:0];
216 [NSArray arrayWithObject:[tmpRelationship sourceAttribute]];
220 [NSArray arrayWithObject:[relationship sourceAttribute]];
223 sourceAttribute = [tmpRelationship sourceAttribute];
224 value = [row objectForKey:[sourceAttribute name]];
226 /* Returns nil if `row' does not contain all the values needed to
227 create a complete qualifier
231 destinationAttribute = [tmpRelationship destinationAttribute];
232 [qualifier->content addObject:
233 [EOQualifierJoinHolder valueForSource:destinationAttribute
236 if (componentRelationships) {
237 EOEntity *tempEntity = [tmpRelationship destinationEntity];
239 /* The relationship is flattened. Iterate over the components and
240 add joins that `link' the components between them.
242 count2 = [componentRelationships count];
243 for (j = 1; j < count2; j++) {
244 relationship = [componentRelationships objectAtIndex:j];
246 if ([relationship sourceAttribute]) {
247 [qualifier->content addObject:@" AND "];
248 [qualifier->content addObject:
249 [EOQualifierJoinHolder valueForSource:
250 [relationship sourceAttribute]
252 [relationship destinationAttribute]]];
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]);
269 ASSIGN(qualifier->entity, [relationship destinationEntity]);
274 + (EOSQLQualifier *)qualifierForObject:sourceObject
275 relationship:(EORelationship *)relationship
277 return [self qualifierForRow:
278 [sourceObject valueForKey:[[relationship sourceAttribute] name]]
279 relationship:relationship];
283 NSZone *z = [self zone];
285 RELEASE(self->content); self->content = nil;
286 RELEASE(self->relationshipPaths); self->relationshipPaths = nil;
287 RELEASE(self->additionalEntities); self->additionalEntities = nil;
289 self->content = [[EOExpressionArray allocWithZone:z] init];
290 self->relationshipPaths = [[NSMutableSet allocWithZone:z] init];
291 self->additionalEntities = [[NSMutableSet allocWithZone:z] init];
295 - (id)initWithEntity:(EOEntity *)_entity
296 qualifierFormat:(NSString *)_qualifierFormat
297 argumentsArray:(NSArray *)_args
299 PrintfFormatScanner *formatScanner = nil;
300 EOQualifierEnumScannerHandler *scannerHandler = nil;
301 NSString *qualifierString = nil;
302 NSMutableArray *myRelationshipPaths = nil;
303 NSEnumerator *args = nil;
305 myRelationshipPaths = [[NSMutableArray allocWithZone:[self zone]] init];
308 ASSIGN(self->entity, _entity);
310 if (_qualifierFormat == nil)
313 formatScanner = [[PrintfFormatScanner alloc] init];
314 scannerHandler = [[EOQualifierEnumScannerHandler alloc] init];
315 [formatScanner setAllowOnlySpecifier:YES];
317 args = [_args objectEnumerator];
318 [scannerHandler setEntity:_entity];
320 [formatScanner setFormatScannerHandler:scannerHandler];
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 !!
329 #if defined(__s390__)
331 [formatScanner performSelector:@selector(stringWithFormat:arguments:)
332 withObject:_qualifierFormat
335 // TODO: args is an NSArray, PrintfFormatScanner expects a va_list?
336 // I think that this is OK because we use EOQualifierEnumScannerHandler
338 [formatScanner stringWithFormat:_qualifierFormat
339 arguments:(void *)args];
342 [formatScanner release]; formatScanner = nil;
343 [scannerHandler release]; scannerHandler = nil;
345 [self->content release]; self->content = nil;
347 [[EOExpressionArray parseExpression:qualifierString
349 replacePropertyReferences:YES
350 relationshipPaths:myRelationshipPaths]
352 [self _computeRelationshipPaths:myRelationshipPaths];
353 [myRelationshipPaths release]; myRelationshipPaths = nil;
357 - (id)initWithEntity:(EOEntity*)_entity
358 qualifierFormat:(NSString *)qualifierFormat, ...
361 id formatScanner = nil;
362 id scannerHandler = nil;
363 NSString *qualifierString = nil;
364 NSMutableArray *myRelationshipPaths = nil;
366 if ((self = [self init]) == nil)
369 myRelationshipPaths = [[NSMutableArray alloc] init];
370 ASSIGN(self->entity, _entity);
372 if (qualifierFormat == nil) {
376 formatScanner = [[PrintfFormatScanner alloc] init];
377 scannerHandler = [[EOQualifierScannerHandler alloc] init];
378 [formatScanner setAllowOnlySpecifier:YES];
380 va_start(ap, qualifierFormat);
381 [scannerHandler setEntity:_entity];
382 [formatScanner setFormatScannerHandler:scannerHandler];
383 qualifierString = [formatScanner stringWithFormat:qualifierFormat
387 [formatScanner release];
388 [scannerHandler release];
390 [self->content release]; self->content = nil;
392 [[EOExpressionArray parseExpression:qualifierString
394 replacePropertyReferences:YES
395 relationshipPaths:myRelationshipPaths] retain];
396 [self _computeRelationshipPaths:myRelationshipPaths];
397 [myRelationshipPaths release]; myRelationshipPaths = nil;
401 - (void)_computeRelationshipPaths {
402 [self _computeRelationshipPaths:nil];
406 handle_attribute(EOSQLQualifier *self, id object, id _relationshipPaths)
408 if ([object isFlattened]) {
409 id definitionArray = nil;
413 definitionArray = [object definitionArray];
414 range = NSMakeRange(0, [definitionArray count] - 1);
415 relsArray = [definitionArray subarrayWithRange:range];
417 [self->relationshipPaths addObject:relsArray];
418 [self->additionalEntities addObjectsFromArray:relsArray];
421 [self->additionalEntities addObject:[object entity]];
425 - (void)_computeRelationshipPaths:(NSArray *)_relationshipPaths {
428 [relationshipPaths removeAllObjects];
430 if (_relationshipPaths) {
431 NSEnumerator *pathEnum = [_relationshipPaths objectEnumerator];
432 NSArray *relPath = nil;
434 while ((relPath = [pathEnum nextObject])) {
435 NSEnumerator *relEnum = nil;
436 EORelationship *rel = nil;
438 relEnum = [relPath objectEnumerator];
440 while ((rel = [relEnum nextObject])) {
441 [additionalEntities addObject:[rel destinationEntity]];
444 [relationshipPaths addObjectsFromArray:_relationshipPaths];
446 for (i = 0, count = [content count]; i < count; i++) {
447 id object = [content objectAtIndex:i];
449 /* The objects from content can only be NSString, values or
451 if ([object isKindOfClass:[EOAttribute class]]) {
452 handle_attribute (self, object, _relationshipPaths);
454 else if ([object isKindOfClass:[EOQualifierJoinHolder class]]) {
456 id destination = nil;
458 source = [object source];
459 destination = [object destination];
461 if ([source isKindOfClass:[EOAttribute class]])
462 handle_attribute (self, source, _relationshipPaths);
463 if ([destination isKindOfClass:[EOAttribute class]])
464 handle_attribute (self, destination, _relationshipPaths);
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];
476 RELEASE(self->relationshipPaths);
477 RELEASE(self->additionalEntities);
479 RELEASE(self->entity);
480 RELEASE(self->content);
485 return [self copyWithZone:NSDefaultMallocZone()];
488 - (id)copyWithZone:(NSZone*)zone {
489 EOSQLQualifier* copy = nil;
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;
500 [self->content insertObject:@"NOT (" atIndex:0];
501 [self->content addObject:@")"];
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"];
511 if (self->entity != qualifier->entity) {
512 [NSException raise:NSInvalidArgumentException
513 format:@"qualifier argument of conjoinWithQualifier: "
514 @"must have the same entity as receiver"];
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];
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"];
531 if (self->entity != qualifier->entity) {
532 [NSException raise:NSInvalidArgumentException
533 format:@"qualifier argument of disjoinWithQualifier: "
534 @"must have the same entity as receiver"];
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];
544 - (EOEntity*)entity {
548 return (self->entity == nil) ? YES : NO;
550 - (void)setUsesDistinct:(BOOL)flag {
551 self->usesDistinct = flag;
553 - (BOOL)usesDistinct {
554 return self->usesDistinct;
556 - (NSMutableSet*)relationshipPaths {
557 return self->relationshipPaths;
559 - (NSMutableSet*)additionalEntities {
560 return self->additionalEntities;
563 - (NSString*)expressionValueForContext:(id<EOExpressionContext>)ctx {
564 return [self->content expressionValueForContext:ctx];
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;
576 - (NSString *)description {
579 ms = [NSMutableString stringWithCapacity:128];
580 [ms appendFormat:@"<0x%08X[%@]:\n", self, NSStringFromClass([self class])];
582 [ms appendFormat:@" entity=%@", [self->entity name]];
585 [ms appendFormat:@" content=%@", self->content];
587 if (self->usesDistinct)
588 [ms appendString:@" distinct"];
590 [ms appendString:@">"];
594 @end /* EOSQLQualifier */