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
336 [formatScanner stringWithFormat:_qualifierFormat
340 [formatScanner release]; formatScanner = nil;
341 [scannerHandler release]; scannerHandler = nil;
343 [self->content release]; self->content = nil;
345 [[EOExpressionArray parseExpression:qualifierString
347 replacePropertyReferences:YES
348 relationshipPaths:myRelationshipPaths]
350 [self _computeRelationshipPaths:myRelationshipPaths];
351 [myRelationshipPaths release]; myRelationshipPaths = nil;
355 - (id)initWithEntity:(EOEntity*)_entity
356 qualifierFormat:(NSString *)qualifierFormat, ...
359 id formatScanner = nil;
360 id scannerHandler = nil;
361 NSString *qualifierString = nil;
362 NSMutableArray *myRelationshipPaths = nil;
364 if ((self = [self init]) == nil)
367 myRelationshipPaths = [[NSMutableArray alloc] init];
368 ASSIGN(self->entity, _entity);
370 if (qualifierFormat == nil) {
374 formatScanner = [[PrintfFormatScanner alloc] init];
375 scannerHandler = [[EOQualifierScannerHandler alloc] init];
376 [formatScanner setAllowOnlySpecifier:YES];
378 va_start(ap, qualifierFormat);
379 [scannerHandler setEntity:_entity];
380 [formatScanner setFormatScannerHandler:scannerHandler];
381 qualifierString = [formatScanner stringWithFormat:qualifierFormat
385 [formatScanner release];
386 [scannerHandler release];
388 [self->content release]; self->content = nil;
390 [[EOExpressionArray parseExpression:qualifierString
392 replacePropertyReferences:YES
393 relationshipPaths:myRelationshipPaths] retain];
394 [self _computeRelationshipPaths:myRelationshipPaths];
395 [myRelationshipPaths release]; myRelationshipPaths = nil;
399 - (void)_computeRelationshipPaths {
400 [self _computeRelationshipPaths:nil];
404 handle_attribute(EOSQLQualifier *self, id object, id _relationshipPaths)
406 if ([object isFlattened]) {
407 id definitionArray = nil;
411 definitionArray = [object definitionArray];
412 range = NSMakeRange(0, [definitionArray count] - 1);
413 relsArray = [definitionArray subarrayWithRange:range];
415 [self->relationshipPaths addObject:relsArray];
416 [self->additionalEntities addObjectsFromArray:relsArray];
419 [self->additionalEntities addObject:[object entity]];
423 - (void)_computeRelationshipPaths:(NSArray *)_relationshipPaths {
426 [relationshipPaths removeAllObjects];
428 if (_relationshipPaths) {
429 NSEnumerator *pathEnum = [_relationshipPaths objectEnumerator];
430 NSArray *relPath = nil;
432 while ((relPath = [pathEnum nextObject])) {
433 NSEnumerator *relEnum = nil;
434 EORelationship *rel = nil;
436 relEnum = [relPath objectEnumerator];
438 while ((rel = [relEnum nextObject])) {
439 [additionalEntities addObject:[rel destinationEntity]];
442 [relationshipPaths addObjectsFromArray:_relationshipPaths];
444 for (i = 0, count = [content count]; i < count; i++) {
445 id object = [content objectAtIndex:i];
447 /* The objects from content can only be NSString, values or
449 if ([object isKindOfClass:[EOAttribute class]]) {
450 handle_attribute (self, object, _relationshipPaths);
452 else if ([object isKindOfClass:[EOQualifierJoinHolder class]]) {
454 id destination = nil;
456 source = [object source];
457 destination = [object destination];
459 if ([source isKindOfClass:[EOAttribute class]])
460 handle_attribute (self, source, _relationshipPaths);
461 if ([destination isKindOfClass:[EOAttribute class]])
462 handle_attribute (self, destination, _relationshipPaths);
464 else if ([object isKindOfClass:[EORelationship class]]) {
465 [[[InvalidPropertyException alloc]
466 initWithFormat:@"cannot refer a EORelat"
467 @"ionship in a EOSQLQualifier: '%@'",
468 [(EORelationship*)object name]] raise];
474 RELEASE(self->relationshipPaths);
475 RELEASE(self->additionalEntities);
477 RELEASE(self->entity);
478 RELEASE(self->content);
483 return [self copyWithZone:NSDefaultMallocZone()];
486 - (id)copyWithZone:(NSZone*)zone {
487 EOSQLQualifier* copy = nil;
489 copy = [[self->isa allocWithZone:zone] init];
490 copy->entity = RETAIN(self->entity);
491 copy->content = [self->content mutableCopyWithZone:zone];
492 copy->relationshipPaths = [self->relationshipPaths mutableCopyWithZone:zone];
493 copy->usesDistinct = self->usesDistinct;
498 [self->content insertObject:@"NOT (" atIndex:0];
499 [self->content addObject:@")"];
502 - (void)conjoinWithQualifier:(EOSQLQualifier*)qualifier {
503 if (![qualifier isKindOfClass:[EOSQLQualifier class]]) {
504 [NSException raise:NSInvalidArgumentException
505 format:@"argument of conjoinWithQualifier: method must "
506 @"be EOSQLQualifier"];
509 if (self->entity != qualifier->entity) {
510 [NSException raise:NSInvalidArgumentException
511 format:@"qualifier argument of conjoinWithQualifier: "
512 @"must have the same entity as receiver"];
515 [self->content insertObject:@"(" atIndex:0];
516 [self->content addObject:@") AND ("];
517 [self->content addObjectsFromExpressionArray:qualifier->content];
518 [self->content addObject:@")"];
519 [self->relationshipPaths unionSet:qualifier->relationshipPaths];
522 - (void)disjoinWithQualifier:(EOSQLQualifier*)qualifier {
523 if (![qualifier isKindOfClass:[EOSQLQualifier class]]) {
524 [NSException raise:NSInvalidArgumentException
525 format:@"argument of disjoinWithQualifier: method must "
526 @"be EOSQLQualifier"];
529 if (self->entity != qualifier->entity) {
530 [NSException raise:NSInvalidArgumentException
531 format:@"qualifier argument of disjoinWithQualifier: "
532 @"must have the same entity as receiver"];
535 [self->content insertObject:@"(" atIndex:0];
536 [self->content addObject:@") OR ("];
537 [self->content addObjectsFromExpressionArray:qualifier->content];
538 [self->content addObject:@")"];
539 [self->relationshipPaths unionSet:qualifier->relationshipPaths];
542 - (EOEntity*)entity {
546 return (self->entity == nil) ? YES : NO;
548 - (void)setUsesDistinct:(BOOL)flag {
549 self->usesDistinct = flag;
551 - (BOOL)usesDistinct {
552 return self->usesDistinct;
554 - (NSMutableSet*)relationshipPaths {
555 return self->relationshipPaths;
557 - (NSMutableSet*)additionalEntities {
558 return self->additionalEntities;
561 - (NSString*)expressionValueForContext:(id<EOExpressionContext>)ctx {
562 return [self->content expressionValueForContext:ctx];
565 - (EOSQLQualifier *)sqlQualifierForEntity:(EOEntity *)_entity {
566 NSAssert3(self->entity == _entity,
567 @"passed invalid entity to %@ (contains %@, got %@)",
568 self, self->entity, _entity);
569 return (EOSQLQualifier *)self;
574 - (NSString *)description {
577 ms = [NSMutableString stringWithCapacity:128];
578 [ms appendFormat:@"<0x%08X[%@]:\n", self, NSStringFromClass([self class])];
580 [ms appendFormat:@" entity=%@", [self->entity name]];
583 [ms appendFormat:@" content=%@", self->content];
585 if (self->usesDistinct)
586 [ms appendString:@" distinct"];
588 [ms appendString:@">"];
592 @end /* EOSQLQualifier */