/* EOSQLExpression.m Copyright (C) 1996 Free Software Foundation, Inc. Author: Ovidiu Predescu Date: September 1996 This file is part of the GNUstep Database Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "EOSQLExpression.h" #include "common.h" #include "EOAdaptor.h" #include "EOAdaptorChannel.h" #include "EOAdaptorContext.h" #include "EOAttribute.h" #include "EOAttributeOrdering.h" #include "EOEntity.h" #include "EOFExceptions.h" #include "EOSQLQualifier.h" #include "EORelationship.h" #include #include #include #include #if LIB_FOUNDATION_LIBRARY # include # include #else # include "DefaultScannerHandler.h" # include "PrintfFormatScanner.h" #endif /* A SQL command is generated for an entity when you fetch, insert, update or delete an object from the database. The command includes all the attributes from entity. The biggest problem in generation of SQL commands is the generation of SELECT statements, because the tables have to be properly aliased. If an entity has only simple attributes then you have a single table in the FROM clause. If the entity has flattened attributes, then several tables appear in the FROM list; each one with a different alias. The algorithm uses a dictionary that has as keys either entities or relationships. The values in dictionary are aliases. For a simple attribute we insert in the above dictionary the attribute's entity as key and an alias for it. For a flattened attribute with the following definition: `toEntity1.toEntity2. ... toEntityn.attribute', we have to assign to each toEntityi object an alias. Let's see how each component of the SELECT command is generated. The columns list is generated by iterating over the attributes of entity. If the attribute is simple, its entity is looked up in the aliases dictionary. If the attribute is flattened, the alias of attribute is the alias of toEntityn. If the attribute is derived, each component of it is generated applying the same rules. The FROM list is generated like this. For each key in the aliases dictionary: * if the key is an entity, its external name is written followed by the alias corresponding to entity * if the key is a relationship, the external name of its destination entity is written followed by the alias corresponding to relationship. A little bit complicated is the WHERE clause. For each flattened attribute we have to generate the logic expression that selects the attribute by expressing the joins over several entities. The algorithm starts with the first relationship. An additional variable, named context is used. Its initial value is the current entity. The expression context-alias.source-attribute join-operator relationship-alias.destination-attribute is added using the AND operator to the where clause. Then the context variable is set to the current relationship. The algorithm continues with the next relationship until there are no more relationships to process. */ static EONull *null = nil; NSString *EOBindVariableNameKey = @"name"; NSString *EOBindVariablePlaceHolderKey = @"placeHolder"; NSString *EOBindVariableAttributeKey = @"attribute"; NSString *EOBindVariableValueKey = @"value"; @interface EOInsertUpdateScannerHandler : DefaultScannerHandler { NSString *value; EOAttribute *attribute; EOAdaptor *adaptor; } - (void)setValue:(NSString*)value attribute:(EOAttribute*)attribute adaptor:(EOAdaptor*)adaptor; @end @implementation EOInsertUpdateScannerHandler - (id)init { [super init]; specHandler['V'] = [self methodForSelector:@selector(convertValue:scanner:)]; return self; } - (void)dealloc { RELEASE(self->value); RELEASE(self->attribute); RELEASE(self->adaptor); [super dealloc]; } - (void)setValue:(NSString *)_value attribute:(EOAttribute*)_attribute adaptor:(EOAdaptor*)_adaptor { ASSIGNCOPY(self->value, _value); ASSIGN(self->attribute, _attribute); ASSIGN(self->adaptor, _adaptor); } - (NSString *)convertValue:(va_list *)pString scanner:(FormatScanner *)scanner{ return (self->adaptor) ? [self->adaptor formatValue:(self->value ? self->value : (NSString *)null) forAttribute:attribute] : (id)self->value; } @end /* EOInsertUpdateScannerHandler */ @implementation EOSQLExpression + (int)version { return 1; } + (void)initialize { if (null == nil) null = [[NSNull null] retain]; } - (id)initWithEntity:(EOEntity *)_entity { if ((self = [super init])) { ASSIGN(self->entity, _entity); } return self; } - (id)init { return [self initWithEntity:nil]; } + (id)deleteExpressionWithQualifier:(EOSQLQualifier*)qualifier channel:(EOAdaptorChannel*)channel { EOSQLExpression* sqlExpression = AUTORELEASE([[self deleteExpressionClass] new]); [sqlExpression deleteExpressionWithQualifier:qualifier channel:channel]; return sqlExpression; } + (id)insertExpressionForRow:(NSDictionary *)row entity:(EOEntity*)_entity channel:(EOAdaptorChannel*)channel { EOSQLExpression* sqlExpression = AUTORELEASE([[self insertExpressionClass] new]); [sqlExpression insertExpressionForRow:row entity:_entity channel:channel]; return sqlExpression; } - (id)deleteExpressionWithQualifier:(EOSQLQualifier *)qualifier channel:(EOAdaptorChannel*)channel { NSString *sql; self = [self initWithEntity:[qualifier entity]]; self->adaptor = RETAIN([[channel adaptorContext] adaptor]); sql = [self assembleDeleteStatementWithQualifier:qualifier tableList:[entity externalName] whereClause:[self whereClauseForQualifier:qualifier]]; self->content = [sql mutableCopy]; [self finishBuildingExpression]; return self; } - (id)insertExpressionForRow:(NSDictionary *)row entity:(EOEntity*)_entity channel:(EOAdaptorChannel*)channel { NSString *sql; self = [self initWithEntity:_entity]; self->adaptor = RETAIN([[channel adaptorContext] adaptor]); sql = [self assembleInsertStatementWithRow:row tableList:[entity externalName] columnList:[self columnListForRow:row] valueList:[self valueListForRow:row]]; self->content = [sql mutableCopy]; [self finishBuildingExpression]; return self; } + (id)selectExpressionForAttributes:(NSArray *)attributes lock:(BOOL)flag qualifier:(EOSQLQualifier *)qualifier fetchOrder:(NSArray *)fetchOrder channel:(EOAdaptorChannel *)channel { EOSQLExpression* sqlExpression = AUTORELEASE([[self selectExpressionClass] new]); [sqlExpression selectExpressionForAttributes:attributes lock:flag qualifier:qualifier fetchOrder:fetchOrder channel:channel]; return sqlExpression; } - (id)selectExpressionForAttributes:(NSArray *)attributes lock:(BOOL)flag qualifier:(EOSQLQualifier *)qualifier fetchOrder:(NSArray *)fetchOrder channel:(EOAdaptorChannel *)channel { NSArray *relationshipPaths; NSString *sql; self = [self initWithEntity:[qualifier entity]]; self->adaptor = [[[channel adaptorContext] adaptor] retain]; // self->content = [NSMutableString new]; // mh: BUG!!! relationshipPaths = [self relationshipPathsForAttributes:attributes qualifier:qualifier fetchOrder:fetchOrder]; sql = [self assembleSelectStatementWithAttributes:attributes lock:flag qualifier:qualifier fetchOrder:fetchOrder selectString: [qualifier usesDistinct] ? @"SELECT DISTINCT" : @"SELECT" columnList: [self selectListWithAttributes:attributes qualifier:qualifier] tableList:[self fromClause] whereClause:[self whereClauseForQualifier:qualifier] joinClause: [self joinExpressionForRelationshipPaths:relationshipPaths] orderByClause:[self orderByClauseForFetchOrder:fetchOrder] lockClause:flag ? [self lockClause] : (NSString *)nil]; self->content = [sql mutableCopy]; [self finishBuildingExpression]; return self; } + (id)updateExpressionForRow:(NSDictionary *)row qualifier:(EOSQLQualifier *)qualifier channel:(EOAdaptorChannel *)channel { EOSQLExpression* sqlExpression = AUTORELEASE([[self updateExpressionClass] new]); [sqlExpression updateExpressionForRow:row qualifier:qualifier channel:channel]; return sqlExpression; } - (id)updateExpressionForRow:(NSDictionary *)row qualifier:(EOSQLQualifier *)qualifier channel:(EOAdaptorChannel *)channel { NSString *sql; self = [self initWithEntity:[qualifier entity]]; if (self->entity == nil) { [[[InvalidQualifierException alloc] initWithFormat:@"entity for qualifier %@ is nil", [qualifier expressionValueForContext:nil]] raise]; } self->adaptor = [[[channel adaptorContext] adaptor] retain]; self->content = [[NSMutableString alloc] init]; sql = [self assembleUpdateStatementWithRow:row qualifier:qualifier tableList:[entity externalName] updateList:[self updateListForRow:row] whereClause:[self whereClauseForQualifier:qualifier]]; [self->content appendString:sql]; [self finishBuildingExpression]; return self; } - (void)dealloc { RELEASE(self->bindings); RELEASE(self->listString); RELEASE(self->whereClauseString); RELEASE(self->entity); RELEASE(self->adaptor); RELEASE(self->entitiesAndPropertiesAliases); RELEASE(self->fromListEntities); RELEASE(self->content); [super dealloc]; } - (NSString *)selectListWithAttributes:(NSArray *)attributes qualifier:(EOSQLQualifier *)qualifier { NSMutableString *selectList; NSEnumerator *enumerator; BOOL first = YES; EOAttribute *attribute; BOOL isDistinct; selectList = [NSMutableString stringWithCapacity:128]; isDistinct = [qualifier usesDistinct]; /* check whether DISTINCT is allowed */ enumerator = [attributes objectEnumerator]; while ((attribute = [enumerator nextObject])) { if (self->adaptor) { if (![self->adaptor attributeAllowedInDistinctSelects:attribute]) { #if DEBUG && 0 NSLog(@"WARNING: tried to select attribute type %@ with DISTINCT " @"which is not allowed, disabling DISTINCT: %@", [attribute externalType], [attribute name]); #endif isDistinct = NO; break; } } } enumerator = [attributes objectEnumerator]; while ((attribute = [enumerator nextObject])) { if(first) first = NO; else [selectList appendString:@", "]; [selectList appendString:[self expressionValueForAttribute:attribute]]; } return selectList; } - (NSString *)fromClause { NSMutableString *fromClause; NSEnumerator *enumerator; BOOL first = YES; id key; fromClause = [NSMutableString stringWithCapacity:64]; enumerator = [self->fromListEntities objectEnumerator]; /* Compute the FROM list from all the aliases found in entitiesAndPropertiesAliases dictionary. Note that this dictionary contains entities and relationships. The last ones are there for flattened attributes over reflexive relationships. */ while ((key = [enumerator nextObject])) { if (first) first = NO; else [fromClause appendString:@", "]; if ([key isKindOfClass:[EORelationship class]]) { /* This key corresponds to a flattened attribute. */ [fromClause appendFormat:@"%@ %@", [[key destinationEntity] externalName], [entitiesAndPropertiesAliases objectForKey:key]]; } else { /* This is an EOEntity. */ [fromClause appendFormat:@"%@ %@", [key externalName], [entitiesAndPropertiesAliases objectForKey:key]]; } } return fromClause; } - (NSString *)whereClauseForQualifier:(EOSQLQualifier *)_qualifier { return [_qualifier expressionValueForContext:self]; } - (NSString *)joinExpressionForRelationshipPaths:(NSArray *)relationshipPaths { NSMutableString *expression; NSEnumerator *enumerator; NSMutableArray *rels; NSArray *relationshipPath; EORelationship *relationship; BOOL first = YES; expression = [NSMutableString stringWithCapacity:64]; enumerator = [relationshipPaths objectEnumerator]; rels = [[NSMutableArray alloc] initWithCapacity:4]; while ((relationshipPath = [enumerator nextObject]) != nil) { NSEnumerator *componentRelationshipsEnumerator; id context; componentRelationshipsEnumerator = [relationshipPath objectEnumerator]; context = entity; while((relationship = [componentRelationshipsEnumerator nextObject])) { if (![rels containsObject:relationship]) { id sourceAttribute, destinationAttribute; [rels addObject:relationship]; /* Compute the SQL expression string corresponding to each join in the relationship. */ sourceAttribute = [self expressionValueForAttribute: [relationship sourceAttribute] context:context]; destinationAttribute = [self expressionValueForAttribute: [relationship destinationAttribute] context: [relationship destinationEntity]]; //NSLog(@"sourceAttribute %@", sourceAttribute); //NSLog(@"destinationAttribute %@", destinationAttribute); if (first) first = NO; else [expression appendString:@" AND "]; [expression appendString:[sourceAttribute description]]; [expression appendString:@" = "]; [expression appendString:[destinationAttribute description]]; } /* Compute the next context which is the current relationship. */ context = [relationship destinationEntity]; } } [rels release]; return expression; } - (NSString *)orderByClauseForFetchOrder:(NSArray *)fetchOrder { int i, count; NSMutableString *orderBy; if ((count = [fetchOrder count]) == 0) return @""; orderBy = (id)[NSMutableString stringWithCapacity:32]; for(i = 0; i < count; i++) { id eorder; eorder = [fetchOrder objectAtIndex:i]; if (i != 0) [orderBy appendString:@", "]; if ([eorder isKindOfClass:[EOSortOrdering class]]) { EOSortOrdering *order; EOAttribute *attribute; SEL ordering; NSString *fmt; order = eorder; ordering = [order selector]; attribute = [self->entity attributeNamed:[order key]]; if (sel_eq(ordering, EOCompareCaseInsensitiveAscending) || sel_eq(ordering, EOCompareCaseInsensitiveDescending)) fmt = @"LOWER(%@)"; else fmt = @"%@"; [orderBy appendFormat:fmt, [self expressionValueForAttribute:attribute]]; if (sel_eq(ordering, EOCompareCaseInsensitiveAscending) || sel_eq(ordering, EOCompareAscending)) { [orderBy appendString:@" ASC"]; } else if (sel_eq(ordering, EOCompareCaseInsensitiveDescending) || sel_eq(ordering, EOCompareDescending)) { [orderBy appendString:@" DESC"]; } } else { EOAttributeOrdering *order; EOOrdering ordering; order = eorder; ordering = [order ordering]; [orderBy appendFormat:@"%@", [self expressionValueForAttribute:[order attribute]]]; if (ordering != EOAnyOrder) [orderBy appendString: ([order ordering] == EOAscendingOrder ? @" ASC" : @" DESC")]; } } return orderBy; } - (NSString *)literalForAttribute:(EOAttribute *)_attribute withValue:(id)_value fromRow:(NSDictionary *)_row { return (self->adaptor) ? [self->adaptor formatValue:(_value ? _value : (id)null) forAttribute:_attribute] : (id)[_value stringValue]; } - (id)updateListForRow:(NSDictionary *)row { PrintfFormatScanner *formatScanner = nil; EOInsertUpdateScannerHandler *scannerHandler = nil; NSMutableString *expression = nil; NSEnumerator *enumerator; NSString *attributeName = nil; BOOL first = YES; enumerator = [row keyEnumerator]; expression = [NSMutableString stringWithCapacity:256]; formatScanner = [[PrintfFormatScanner alloc] init]; AUTORELEASE(formatScanner); scannerHandler = [[EOInsertUpdateScannerHandler alloc] init]; AUTORELEASE(scannerHandler); [formatScanner setAllowOnlySpecifier:YES]; [formatScanner setFormatScannerHandler:scannerHandler]; while((attributeName = [enumerator nextObject])) { EOAttribute *attribute; NSString *columnName = nil; id value = nil; attribute = [entity attributeNamed:attributeName]; NSAssert1(attribute, @"attribute %@ should be non nil", attributeName); columnName = adaptor ? (NSString *)[adaptor formatAttribute:attribute] : [attribute columnName]; value = [row objectForKey:attributeName]; value = [self literalForAttribute:attribute withValue:value fromRow:row]; if (first) first = NO; else [expression appendString:@", "]; [expression appendString:columnName]; [expression appendString:@" = "]; [expression appendString:value]; } return expression; } - (id)columnListForRow:(NSDictionary *)row { NSMutableString *expression; NSEnumerator *enumerator; NSString *attributeName = nil; BOOL first = YES; expression = [NSMutableString stringWithCapacity:128]; enumerator = [row keyEnumerator]; while ((attributeName = [enumerator nextObject])) { EOAttribute *attribute; NSString *columnName; attribute = [entity attributeNamed:attributeName]; NSAssert1(attribute, @"attribute %@ should be non nil", attributeName); columnName = adaptor ? (NSString *)[adaptor formatAttribute:attribute] : [attribute columnName]; if (first) first = NO; else [expression appendString:@", "]; [expression appendString:columnName]; } return expression; } - (id)valueListForRow:(NSDictionary *)row { EOInsertUpdateScannerHandler *scannerHandler; PrintfFormatScanner *formatScanner; NSEnumerator *enumerator; NSString *attributeName = nil; NSMutableString *expression = nil; BOOL first = YES; formatScanner = [[PrintfFormatScanner alloc] init]; AUTORELEASE(formatScanner); scannerHandler = [[EOInsertUpdateScannerHandler alloc] init]; AUTORELEASE(scannerHandler); expression = [NSMutableString stringWithCapacity:256]; enumerator = [row keyEnumerator]; [formatScanner setAllowOnlySpecifier:YES]; [formatScanner setFormatScannerHandler:scannerHandler]; while ((attributeName = [enumerator nextObject])) { EOAttribute *attribute; id value; attribute = [entity attributeNamed:attributeName]; value = [row objectForKey:attributeName]; NSAssert1(attribute, @"attribute %@ should be non nil", attributeName); value = [self literalForAttribute:attribute withValue:value fromRow:row]; if (first) first = NO; else [expression appendString:@", "]; [expression appendString:value]; } return expression; } - (NSArray *)relationshipPathsForAttributes:(NSArray *)attributes qualifier:(EOSQLQualifier *)qualifier fetchOrder:(NSArray *)fetchOrder { int i, count; NSMutableSet *entities; NSMutableSet *relationshipPaths; NSEnumerator *enumerator; id entityOrRelationship; entities = [NSMutableSet set]; relationshipPaths = [NSMutableSet set]; NSAssert3(self->entity, @"entity should be non nil (attrs=%@, qual=%@, order=%@)", attributes, qualifier, fetchOrder); for (i = 0, count = [attributes count]; i < count; i++) { EOAttribute *attribute; attribute = [attributes objectAtIndex:i]; if ([attribute entity] != entity) { [[[InvalidAttributeException alloc] initWithFormat:@"all attributes must be from the same " @"entity (attribute '%@' is not in '%@')", [attribute name], [entity name]] raise]; } /* attribute is normal. */ [entities addObject:[attribute entity]]; } [relationshipPaths unionSet:[qualifier relationshipPaths]]; [entities unionSet:[qualifier additionalEntities]]; for (i = 0, count = [fetchOrder count]; i < count; i++) { EOAttribute *attribute; id eorder; eorder = [fetchOrder objectAtIndex:i]; attribute = ([eorder isKindOfClass:[EOSortOrdering class]]) ? [self->entity attributeNamed:[(EOSortOrdering *)eorder key]] : [(EOAttributeOrdering *)eorder attribute]; if ([attribute entity] != entity) { [[[InvalidAttributeException alloc] initWithFormat:@"all attributes must be from the same " @"entity (attribute '%@' is not in '%@')", [attribute name], [entity name]] raise]; } } entitiesAndPropertiesAliases = [NSMutableDictionary new]; fromListEntities = [NSMutableArray new]; enumerator = [entities objectEnumerator]; i = 1; while ((entityOrRelationship = [enumerator nextObject])) { NSString* alias = [NSString stringWithFormat:@"t%d", i++]; [entitiesAndPropertiesAliases setObject:alias forKey:entityOrRelationship]; [fromListEntities addObject:entityOrRelationship]; } return [relationshipPaths allObjects]; } - (EOEntity *)entity { return self->entity; } - (id)finishBuildingExpression { return self; } - (NSString *)lockClause { return @""; } - (NSString *)expressionValueForAttribute:(EOAttribute *)attribute context:(id)context { NSString *alias = [entitiesAndPropertiesAliases objectForKey:context]; NSString *columnName = nil; columnName = adaptor ? (NSString *)[adaptor formatAttribute:attribute] : [attribute columnName]; return alias ? (NSString *)[NSString stringWithFormat:@"%@.%@", alias, columnName] : columnName; } - (NSString *)expressionValueForAttribute:(EOAttribute *)attribute { /* attribute is a normal attribute. Its alias is the alias of its entity. */ return [self expressionValueForAttribute:attribute context:[attribute entity]]; } - (NSString *)expressionValueForAttributePath:(NSArray *)definitionArray { /* Take the alias of the last relationship. */ id relationship; NSString *alias; relationship = [definitionArray objectAtIndex:([definitionArray count] - 2)]; alias = [entitiesAndPropertiesAliases objectForKey:relationship]; return AUTORELEASE(([[NSString alloc] initWithFormat:@"%@.%@", alias, [[definitionArray lastObject] columnName]])); } - (NSString *)expressionValueForContext:(id)context { return self->content; } + (Class)selectExpressionClass { return [EOSelectSQLExpression class]; } + (Class)insertExpressionClass { return [EOInsertSQLExpression class]; } + (Class)deleteExpressionClass { return [EODeleteSQLExpression class]; } + (Class)updateExpressionClass { return [EOUpdateSQLExpression class]; } - (EOAdaptor *)adaptor { return self->adaptor; } /* description */ - (NSString *)description { NSMutableString *ms; ms = [NSMutableString stringWithCapacity:128]; [ms appendFormat:@"<0x%p[%@]:\n", self, NSStringFromClass([self class])]; if (self->entity) [ms appendFormat:@" entity=%@\n", self->entity]; if (self->adaptor) [ms appendFormat:@" adaptor=%@\n", self->adaptor]; if (self->content) [ms appendFormat:@" content='%@\n'", self->content]; if (self->entitiesAndPropertiesAliases) [ms appendFormat:@" aliases=%@\n", self->entitiesAndPropertiesAliases]; if (self->fromListEntities) [ms appendFormat:@" from-entities=%@\n", self->fromListEntities]; if (self->whereClauseString) [ms appendFormat:@" where=%@\n", self->whereClauseString]; if (self->listString) [ms appendFormat:@" list=%@\n", self->listString]; if (self->bindings) [ms appendFormat:@" bindings=%@\n", self->bindings]; [ms appendString:@">"]; return ms; } @end /* EOSQLExpression */ @implementation EOInsertSQLExpression @end /* EOInsertSQLExpression */ @implementation EOUpdateSQLExpression @end /* EOUpdateSQLExpression */ @implementation EODeleteSQLExpression @end /* EODeleteSQLExpression */ @implementation EOSQLExpression(NewInEOF2) + (EOSQLExpression *)selectStatementForAttributes:(NSArray *)_attributes lock:(BOOL)_flag fetchSpecification:(EOFetchSpecification *)_fspec entity:(EOEntity *)_entity { if (_fspec == nil) { [NSException raise:NSInvalidArgumentException format:@"missing fetch specification argument .."]; } if ([_attributes count] == 0) { [NSException raise:NSInvalidArgumentException format:@"missing attributes for select .."]; } return [self selectExpressionForAttributes:_attributes lock:_flag qualifier:[[_fspec qualifier] sqlQualifierForEntity:_entity] fetchOrder:[_fspec sortOrderings] channel:nil]; } + (EOSQLExpression *)expressionForString:(NSString *)_sql { EOSQLExpression *se; se = [[EOSQLExpression alloc] init]; [se setStatement:_sql]; return AUTORELEASE(se); } /* accessors */ - (void)setStatement:(NSString *)_stmt { id tmp; tmp = self->content; self->content = [_stmt mutableCopy]; RELEASE(tmp); } - (NSString *)statement { return self->content; } - (NSString *)whereClauseString { return self->whereClauseString; } /* tables */ - (NSString *)tableListWithRootEntity:(EOEntity *)_entity { return ([self->fromListEntities count] > 0) ? [self fromClause] : [_entity externalName]; } /* assembly */ - (NSString *)assembleDeleteStatementWithQualifier:(EOQualifier *)_qualifier tableList:(NSString *)_tableList whereClause:(NSString *)_whereClause { NSMutableString *s; s = [NSMutableString stringWithCapacity:64]; [s appendString:@"DELETE FROM "]; [s appendString:_tableList]; [s appendString:@" WHERE "]; [s appendString:_whereClause]; return s; } - (NSString *)assembleInsertStatementWithRow:(NSDictionary *)_row tableList:(NSString *)_tables columnList:(NSString *)_columns valueList:(NSString *)_values { NSMutableString *s; s = [NSMutableString stringWithCapacity:256]; [s appendString:@"INSERT INTO "]; [s appendString:_tables]; [s appendString:@" ("]; [s appendString:_columns]; [s appendString:@") VALUES ("]; [s appendString:_values]; [s appendString:@")"]; return s; } - (NSString *)assembleSelectStatementWithAttributes:(NSArray *)_attributes lock:(BOOL)_lock qualifier:(EOQualifier *)_qualifier fetchOrder:(NSArray *)_fetchOrder selectString:(NSString *)_selectString columnList:(NSString *)_columns tableList:(NSString *)_tables whereClause:(NSString *)_whereClause joinClause:(NSString *)_joinClause orderByClause:(NSString *)_orderByClause lockClause:(NSString *)_lockClause { NSMutableString *s; unsigned wlen, jlen; #if 0 #warning DEBUG LOG, REMOVE! [self logWithFormat:@"%s: '%@': %@", __PRETTY_FUNCTION__, _whereClause,self]; #endif s = [NSMutableString stringWithCapacity:256]; [s appendString:_selectString ? _selectString : (NSString *)@"SELECT"]; [s appendString:@" "]; [s appendString:_columns]; [s appendString:@" FROM "]; [s appendString:_tables]; if ([_lockClause length] > 0) { [s appendString:@" "]; [s appendString:_lockClause]; } wlen = [_whereClause length]; jlen = [_joinClause length]; if ((wlen > 0) || (jlen > 0)) [s appendString:@" WHERE "]; if (wlen > 0) [s appendString:_whereClause]; if ((wlen > 0) && (jlen > 0)) [s appendString:@" AND "]; if (jlen > 0) [s appendString:_joinClause]; if ([_orderByClause length] > 0) { [s appendString:@" ORDER BY "]; [s appendString:_orderByClause]; } return s; } - (NSString *)assembleUpdateStatementWithRow:(NSDictionary *)_row qualifier:(EOQualifier *)_qualifier tableList:(NSString *)_tables updateList:(NSString *)_updates whereClause:(NSString *)_whereClause { NSMutableString *s; s = [NSMutableString stringWithCapacity:256]; [s appendString:@"UPDATE "]; [s appendString:_tables]; [s appendString:@" SET "]; [s appendString:_updates]; [s appendString:@" WHERE "]; [s appendString:_whereClause]; return s; } - (NSString *)assembleJoinClauseWithLeftName:(NSString *)_leftName rightName:(NSString *)_rightName joinSemantic:(EOJoinSemantic)_semantic { NSMutableString *s; s = [NSMutableString stringWithCapacity:64]; [s appendString:_leftName]; switch (_semantic) { case EOInnerJoin: [s appendString:@" = "]; break; case EOFullOuterJoin: [s appendString:@" *=* "]; break; case EOLeftOuterJoin: [s appendString:@" *= "]; break; case EORightOuterJoin: [s appendString:@" =* "]; break; } [s appendString:_rightName]; return s; } /* attributes */ - (NSString *)sqlStringForAttribute:(EOAttribute *)_attribute { NSLog(@"ERROR(%s): subclasses need to override this method!", __PRETTY_FUNCTION__); return nil; } - (NSString *)sqlStringForAttributePath:(NSString *)_attrPath { NSLog(@"ERROR(%s): subclasses need to override this method!", __PRETTY_FUNCTION__); return nil; } - (NSString *)sqlStringForAttributeNamed:(NSString *)_attrName { EOAttribute *a; if ((a = [[self entity] attributeNamed:_attrName])) return [self sqlStringForAttribute:a]; return [self sqlStringForAttributePath:_attrName]; } /* bind variables */ + (BOOL)useBindVariables { return NO; } - (BOOL)mustUseBindVariableForAttribute:(EOAttribute *)_attr { return NO; } - (BOOL)shouldUseBindVariableForAttribute:(EOAttribute *)_attr { return NO; } - (NSMutableDictionary *)bindVariableDictionaryForAttribute:(EOAttribute *)_a value:(id)_value { NSMutableDictionary *d; d = [NSMutableDictionary dictionaryWithCapacity:8]; [d setObject:_a forKey:EOBindVariableAttributeKey]; [d setObject:_value ? _value : (id)null forKey:EOBindVariableValueKey]; return d; } - (void)addBindVariableDictionary:(NSMutableDictionary *)_dictionary { if (self->bindings == nil) self->bindings = [[NSMutableArray alloc] init]; } - (NSArray *)bindVariableDictionaries { return self->bindings; } /* values */ + (NSString *)formatValue:(id)_value forAttribute:(EOAttribute *)_attribute { return _value != nil ? _value : (id)null; } - (NSString *)sqlStringForValue:(id)_value attributeNamed:(NSString *)_attrName { NSMutableDictionary *bindVars; EOAttribute *attribute; attribute = [[self entity] attributeNamed:_attrName]; if ([self mustUseBindVariableForAttribute:attribute]) bindVars = [self bindVariableDictionaryForAttribute:attribute value:_value]; else if ([[self class] useBindVariables] && [self shouldUseBindVariableForAttribute:attribute]) { bindVars = [self bindVariableDictionaryForAttribute:attribute value:_value]; } else bindVars = nil; if (bindVars) { [self addBindVariableDictionary:bindVars]; return [bindVars objectForKey:EOBindVariablePlaceHolderKey]; } return [[self class] formatValue:(_value ? _value : (id)null) forAttribute:attribute]; } + (NSString *)sqlPatternFromShellPattern:(NSString *)_pattern { unsigned len; if ((len = [_pattern length]) > 0) { unsigned cstrLen = [_pattern cStringLength]; char cstrBuf[cstrLen + 1]; const char *cstr; char buf[len * 3 + 1]; unsigned i; BOOL didSomething = NO; [_pattern getCString:cstrBuf]; cstr = cstrBuf; for (i = 0; *cstr; cstr++) { switch (*cstr) { case '*': buf[i] = '%'; i++; didSomething = YES; break; case '?': buf[i] = '_'; i++; didSomething = YES; break; case '%': buf[i] = '['; i++; buf[i] = '%'; i++; buf[i] = ']'; i++; didSomething = YES; break; case '_': buf[i] = '['; i++; buf[i] = '_'; i++; buf[i] = ']'; i++; didSomething = YES; break; default: buf[i] = *cstr; i++; break; } } buf[i] = '\0'; return (didSomething) ? [NSString stringWithCString:buf length:i] : _pattern; } return _pattern; } /* SQL formats */ + (NSString *)formatSQLString:(NSString *)_sqlString format:(NSString *)_fmt { return _sqlString; } /* qualifier operators */ - (NSString *)sqlStringForSelector:(SEL)_selector value:(id)_value { if ((_value == null) || (_value == nil)) { if (sel_eq(_selector, EOQualifierOperatorEqual)) return @"is"; else if (sel_eq(_selector, EOQualifierOperatorNotEqual)) return @"is not"; } else { if (sel_eq(_selector, EOQualifierOperatorEqual)) return @"="; else if (sel_eq(_selector, EOQualifierOperatorNotEqual)) return @"<>"; } if (sel_eq(_selector, EOQualifierOperatorLessThan)) return @"<"; else if (sel_eq(_selector, EOQualifierOperatorGreaterThan)) return @">"; else if (sel_eq(_selector, EOQualifierOperatorLessThanOrEqualTo)) return @"<="; else if (sel_eq(_selector, EOQualifierOperatorGreaterThanOrEqualTo)) return @">="; else if (sel_eq(_selector, EOQualifierOperatorLike)) return @"LIKE"; else { return [NSString stringWithFormat:@"UNKNOWN<%@>", NSStringFromSelector(_selector)]; } } /* qualifiers */ - (NSString *)sqlStringForKeyComparisonQualifier:(EOKeyComparisonQualifier *)_q { NSMutableString *s; NSString *sql; EOAttribute *a; s = [NSMutableString stringWithCapacity:64]; sql = [self sqlStringForAttributeNamed:[_q leftKey]]; a = [[self entity] attributeNamed:[_q leftKey]]; /* relationships ? */ sql = [[self class] formatSQLString:sql format:nil]; [s appendString:sql]; [s appendString:@" "]; [s appendString:[self sqlStringForSelector:[_q selector] value:nil]]; [s appendString:@" "]; sql = [self sqlStringForAttributeNamed:[_q rightKey]]; a = [[self entity] attributeNamed:[_q rightKey]]; /* relationships ? */ sql = [[self class] formatSQLString:sql format:nil]; [s appendString:sql]; return s; } - (NSString *)sqlStringForKeyValueQualifier:(EOKeyValueQualifier *)_q { NSMutableString *s; NSString *sql; EOAttribute *a; id v; v = [_q value]; s = [NSMutableString stringWithCapacity:64]; sql = [self sqlStringForAttributeNamed:[_q key]]; a = [[self entity] attributeNamed:[_q key]]; /* relationships ? */ sql = [[self class] formatSQLString:sql format:nil]; [s appendString:sql]; [s appendString:@" "]; sql = [self sqlStringForSelector:[_q selector] value:v]; [s appendString:sql]; [s appendString:@" "]; if (([_q selector] == EOQualifierOperatorLike) || ([_q selector] == EOQualifierOperatorCaseInsensitiveLike)) v = [[self class] sqlPatternFromShellPattern:v]; sql = [self sqlStringForValue:v attributeNamed:[_q key]]; [s appendString:sql]; return s; } - (NSString *)sqlStringForNegatedQualifier:(EOQualifier *)_q { NSMutableString *s; NSString *sql; sql = [(id)_q sqlStringForSQLExpression:self]; s = [NSMutableString stringWithCapacity:[sql length] + 8]; [s appendString:@"NOT ("]; [s appendString:sql]; [s appendString:@")"]; return s; } - (NSString *)sqlStringForConjoinedQualifiers:(NSArray *)_qs { NSMutableString *s; unsigned i, count; id (*objAtIdx)(id,SEL,unsigned); objAtIdx = (void *)[_qs methodForSelector:@selector(objectAtIndex:)]; for (i = 0, count = [_qs count], s = nil; i < count; i++) { id q; q = objAtIdx(self, @selector(objectAtIndex:), i); if (s == nil) s = [NSMutableString stringWithCapacity:128]; else [s appendString:@" AND "]; [s appendString:[q sqlStringForSQLExpression:self]]; } return s; } - (NSString *)sqlStringForDisjoinedQualifiers:(NSArray *)_qs { NSMutableString *s; unsigned i, count; id (*objAtIdx)(id,SEL,unsigned); objAtIdx = (void *)[_qs methodForSelector:@selector(objectAtIndex:)]; for (i = 0, count = [_qs count], s = nil; i < count; i++) { id q; q = objAtIdx(self, @selector(objectAtIndex:), i); if (s == nil) s = [NSMutableString stringWithCapacity:128]; else [s appendString:@" OR "]; [s appendString:[q sqlStringForSQLExpression:self]]; } return s; } /* list strings */ - (NSMutableString *)listString { if (self->listString == nil) self->listString = [[NSMutableString alloc] initWithCapacity:128]; return self->listString; } - (void)appendItem:(NSString *)_itemString toListString:(NSMutableString *)_ls { if ([_ls length] > 0) [_ls appendString:@","]; [_ls appendString:_itemString]; } /* deletes */ - (void)prepareDeleteExpressionForQualifier:(EOQualifier *)_qual { NSString *tableList, *sql; self->whereClauseString = [[(id)_qual sqlStringForSQLExpression:self] copy]; tableList = [self tableListWithRootEntity:[self entity]]; sql = [self assembleDeleteStatementWithQualifier:_qual tableList:tableList whereClause:[self whereClauseString]]; [self setStatement:sql]; } /* updates */ - (void)addUpdateListAttribute:(EOAttribute *)_attr value:(NSString *)_value { NSMutableString *s; s = [[NSMutableString alloc] initWithCapacity:32]; [s appendString:[_attr columnName]]; [s appendString:@"="]; _value = [[self class] formatSQLString:_value format:nil]; [s appendString:_value]; [self appendItem:s toListString:[self listString]]; RELEASE(s); } - (void)prepareUpdateExpressionWithRow:(NSDictionary *)_row qualifier:(EOQualifier *)_qual { NSEnumerator *keys; NSString *key; NSString *tableList, *sql; keys = [_row keyEnumerator]; while ((key = [keys nextObject])) { EOAttribute *attribute; id value; attribute = [self->entity attributeNamed:key]; value = [_row objectForKey:key]; [self addUpdateListAttribute:attribute value:value]; } self->whereClauseString = [[(id)_qual sqlStringForSQLExpression:self] copy]; tableList = [self tableListWithRootEntity:[self entity]]; sql = [self assembleUpdateStatementWithRow:_row qualifier:_qual tableList:tableList updateList:[self listString] whereClause:[self whereClauseString]]; [self setStatement:sql]; } @end /* EOSQLExpression(NewInEOF2) */