diff options
Diffstat (limited to 'Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m')
-rw-r--r-- | Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m | 1337 |
1 files changed, 1337 insertions, 0 deletions
diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m new file mode 100644 index 00000000..d3145039 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m @@ -0,0 +1,1337 @@ +// +// $Id: MCPObject.m 927 2009-06-24 10:53:07Z stuart02 $ +// +// MCPObject.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 19/05/04. +// Copyright (c) 2004 Serge Cohen. All rights reserved. +// +// Forked by the Sequel Pro team (sequelpro.com), April 2009 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPObject.h" + +#import <MCPKit/MCPKit.h> + +#import "MCPClassDescription.h" +#import "MCPClassDescription+MCPEntreprise.h" +#import "MCPAttribute.h" +#import "MCPRelation.h" +#import "MCPJoin.h" + +@implementation MCPObject + +#pragma mark Life of the object +- (id) init +/*" Taking care of getting the class description for self (after passing the message up). "*/ +{ +// NSArray theAttributes; +// unsigned int i; + + if (self = [super init]) { + classDescription = [[NSClassDescription classDescriptionForClass:[self class]] retain]; + [self setAttributesToDefault]; +/* + theAttributes = [classDescription attributeKeys]; + for (i=0; [theAttributes count] != i; ++i) { // setting the attributtes with proper defaults. + NSString *theKey = (NSString *) [theAttributes objectAtIndex:i]; + + [self setValue:[self defaultValueForKey:theKey] forKey:theKey]; + } + */ + } + return self; +} + +- (id) initWithDictionary:(NSDictionary *) dictionary +/*" This method will use the class description to fetch in the dictionary the values of the attributes of the object.... + Should try to get this description a bit clearer."*/ +{ + self = [super init]; + if (self) { + unsigned int i; + NSArray *attrArray; + + classDescription = [[NSClassDescription classDescriptionForClass:[self class]] retain]; + [self setAttributesToDefault]; + attrArray = [classDescription attributes]; + for (i=0; [attrArray count] != i; ++i) { + MCPAttribute *currentAttribute = (MCPAttribute *)[attrArray objectAtIndex:i]; + id currentValue = [dictionary objectForKey:[currentAttribute name]]; + + if (! currentValue) { + currentValue = [dictionary objectForKey:[currentAttribute externalName]]; + } + if (currentValue) { + [self setValue:currentValue forKey:[currentAttribute name]]; + } + } + } + return self; +} + +- (void) dealloc +/*" Deallocating the class description, then passes the message to super. "*/ +{ +// unsigned int i; +// NSArray *tmpAttributes = [classDescription attributes]; + +/* for (i=0; [tmpAttributes count] != i; ++i) { + MCPAttribute *tmpAttr = [tmpAttributes objectAtIndex:i]; + if ([tmpAttr valueClass]) { + [self setValue:nil forKey:[tmpAttr name]]; + } + } +*/ + [classDescription release]; + [connection release]; + [super dealloc]; +} + +- (void) setAttributesToDefault +/*" Set all the attributes to default values, except for auto-generated and primary key attributes, which are set to NULL. + + NOTE : !! In the current version the auto-generated and key are ALSO set to default values!!. + "*/ +{ + NSArray *theAttributes = [classDescription attributes]; +// NSArray *thePrimKeys = [classDescription primaryKeyAttributes]; + unsigned i; + + for (i=0; [theAttributes count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [theAttributes objectAtIndex:i]; + NSString *theKey = [theAttribute name]; + + if (! [theAttribute autoGenerated]) { + [self setValue:[self defaultValueForKey:theKey] forKey:theKey]; + } + else { // Auto-generated attribute ... set it to NULL: + [self setValue:[self defaultValueForKey:theKey] forKey:theKey]; + } + } +/* + for (i=0; [thePrimKeys count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [thePrimKeys objectAtIndex:i]; + if (! [theAttribute autoGenerated]) { + NSString *theKey = [theAttribute name]; + + [[self valueForKey:theKey] release]; + [self setValue:NULL forKey:theKey]; + } + } + */ + return; +} + +#pragma mark Accessor(s) +- (MCPClassDescription *) classDescription +{ + return classDescription; +} + +- (MCPConnection *) connection +{ + if ((! connection) || (! [connection checkConnection])) { + [self setConnection:nil]; + } + return connection; +} + +- (void) setConnection:(MCPConnection *) iConnection +{ + if (iConnection != connection) { + [connection release]; + connection = [iConnection retain]; + } +} + + + +#pragma mark Database interface +- (id) readFromDBRow:(NSDictionary *) iDictionary withTableName:(NSString *) iTableName +/*" Uses a query result row (described as a NSDictionary) to set the instance variables of self. If + the result contains columns from multiple tables, the iTableName can be used to specify the alias + used for the table name corresponding to the class. + +If iTableName == nil, the columns will be searched first without table name (column_name) and if + not found then with the table name in front (from the class description : table_name.column_name). + +Otherwise, the search will be performed in the following order : iTableName.column_name, column_name, + table_name.column_name. + "*/ +{ + NSArray *theAttributeKeys = [classDescription attributeKeys]; + NSArray *thePrefixArray; + unsigned i; + +// Depending on the value of iTableName, get the search order. + if ((nil == iTableName) || ([@"" isEqualToString:iTableName])) { + thePrefixArray = [NSArray arrayWithObjects:@"", [NSMutableString stringWithFormat:@"%@.", [classDescription externalName]], nil]; + } + else { + thePrefixArray = [NSArray arrayWithObjects:[NSString stringWithFormat:@"%@.", iTableName], [NSMutableString stringWithFormat:@"%@.", [classDescription externalName]], @"", nil]; + } + for (i=0; [theAttributeKeys count] != i; ++i) { + id theValue = nil; + MCPAttribute *theAttribute = [classDescription attributeWithName:[theAttributeKeys objectAtIndex:i]]; + unsigned j; + + for (j=0; [thePrefixArray count] != j; ++j) { + if (theValue = [iDictionary objectForKey:[NSString stringWithFormat:@"%@%@", [thePrefixArray objectAtIndex:j], [theAttribute externalName]]]) { + break; + } + } + if (theValue) { + [self takeValue:theValue forKey:[theAttribute name]]; + } + } + return self; +} + + +//- (MCPDBReturnCode) setPrimaryKey:(NSDictionary *) iDictionary andFetchFromDB:(MCPConnection *) iConnection +- (MCPDBReturnCode) setPrimaryKey:(id) iDictionary andFetchFromDB:(MCPConnection *) iConnection +/*" This method is used to retrieve an object from the DB given its precise primary key. It will return self. + If the object is not found in the DB, then all instance variable are set to the default + (and autogenerated/primary-key attributes are set to null)."*/ +{ + BOOL missingKey = NO; + NSArray *theKeyAttr = [classDescription primaryKeyAttributes]; + unsigned i; + NSMutableString *query = [NSMutableString stringWithFormat:@"SELECT * FROM %@ WHERE ", [classDescription externalName]]; + MCPResult *result; + NSDictionary *row; + + [self setConnection:iConnection]; + if (! iConnection) { + return MCPDBReturnNoConnection; + } + for (i=0; [theKeyAttr count] != i; ++i) { + MCPAttribute *theAttr = [classDescription attributeWithName:[(MCPAttribute *)[theKeyAttr objectAtIndex:i] name]]; + id theKeyValue; + +// if (theKeyValue = [iDictionary objectForKey:[theAttr name]]) { + if (theKeyValue = [iDictionary valueForKey:[theAttr name]]) { + if (i != 0) { + [query appendString:@" and "]; + } +// Implies the iDictionary IS a dictionary: +// [query appendFormat:@"(%@ = %@)", [theAttr externalName], [iConnection quoteObject:[iDictionary objectForKey:[theAttr name]]]]; +// If the iDictionary is just an object complying with NSValueCodeing: + [query appendFormat:@"(%@ = %@)", [theAttr externalName], [iConnection quoteObject:theKeyValue]]; + } + else { // Part of the primary key is missing... look for the DB name of the attribute + if (theKeyValue = [iDictionary valueForKey:[theAttr externalName]]) { + if (i != 0) { + [query appendString:@" and "]; + } + [query appendFormat:@"(%@ = %@)", [theAttr externalName], [iConnection quoteObject:theKeyValue]]; + } + else { // Not able to find the value for this attribute !!! + missingKey = YES; + NSLog(@"Unable to find the value for attribute %@ of object of class %@, will make it default", [theAttr name], [self className]); + break; + } + } + } // Now the query is prepared... or a key part is missing: + if (missingKey) { + [self setAttributesToDefault]; + return MCPDBReturnIncompleteKey; + } + result = [iConnection queryString:query]; + if ([result numOfRows] == 0) { + [self setAttributesToDefault]; + return MCPDBReturnNone; + } + row = [result fetchRowAsDictionary]; + [self readFromDBRow:row withTableName:@""]; + if ([result numOfRows] != 1) { + NSLog(@"Got more than one row when querying : %@.... will take only the first one!!! that an IMPORTANT flaw in your data model!!!", query); + return MCPDBReturnMultiple; + } + return MCPDBReturnOK; +} + + +- (NSDictionary *) checkDBId +/*" Using the identity properties of the class, this method will check if self already exists in the DB, + in which case it will set the primary key attributes to match the DB entry. If the object is not present + in the DB, the primary key attributes are set to null if they are declared as aut-generated (untouched otherwise). + +The returned dictionary contains the values of the primary key attributes. It also contains one entry with + key MCPDBReturnCode, which contains the result of the operation (was the object in DB?). + +If the identityAttributes of the class description is empty, the entry will always be considered not to be + in the DB, AND the primary key attributes of the object will be left unchnaged and returned as they are at + call time."*/ +{ + NSArray *theIdAttr = [classDescription identityAttributes]; + NSMutableDictionary *theKeys = [NSMutableDictionary dictionary]; + NSArray *theKeyAttr = [classDescription primaryKeyAttributes]; + MCPConnection *theConnection = [self connection]; + MCPResult *theResult; + unsigned int i; + + if (! theConnection) { + return [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:MCPDBReturnNoConnection], @"MCPDBReturnCode"]; + } + if (! [theKeyAttr count]) { // There is no primary key for this object. + [theKeys setObject:[NSNumber numberWithInt:MCPDBReturnNoKey] forKey:@"MCPDBReturnCode"]; + return [NSDictionary dictionaryWithDictionary:theKeys]; + } + if (! [theIdAttr count]) { // Identity is not defined for this object. + [theKeys setObject:[NSNumber numberWithInt:MCPDBReturnNoIdentity] forKey:@"MCPDBReturnCode"]; + for (i=0; [theKeyAttr count] != i; ++i) { + NSString *theKey = [(MCPAttribute*)[theKeyAttr objectAtIndex:i] name]; + [theKeys setObject:[self valueForKey:theKey] forKey:theKey]; + } + return [NSDictionary dictionaryWithDictionary:theKeys]; + } +// Do the fetch in the DB. + NSMutableString *theQuery = [NSMutableString stringWithString:@"SELECT "]; + + for (i = 0; [theKeyAttr count] != i; ++i) { + if (i) { + [theQuery appendString:@", "]; + } + [theQuery appendString:[(MCPAttribute *)[theKeyAttr objectAtIndex:i] externalName]]; + } + [theQuery appendFormat:@" FROM %@ WHERE ", [classDescription externalName]]; + for (i = 0; [theIdAttr count] != i; ++i) { + if (i) { + [theQuery appendString:@" AND "]; + } +/* + if ([[(MCPAttribute *)[theIdAttr objectAtIndex:i] valueClass] isSubclassOfClass:[NSString class]]) { + if ([(MCPAttribute *)[theIdAttr objectAtIndex:i] width] != 0) { + [theQuery appendFormat:@"(BINARY %@ = SUBSTRING(%@ FROM 1 FOR %u)", [(MCPAttribute *)[theIdAttr objectAtIndex:i] externalName], [theConnection quoteObject:[self valueForKey:[(MCPAttribute *)[theIdAttr objectAtIndex:i] name]]], [(MCPAttribute *)[theIdAttr objectAtIndex:i] width]]; + } + else { + [theQuery appendFormat:@"(BINARY %@ = %@)", [(MCPAttribute *)[theIdAttr objectAtIndex:i] externalName], [theConnection quoteObject:[self valueForKey:[(MCPAttribute *)[theIdAttr objectAtIndex:i] name]]]]; + } + } + */ + if (([[(MCPAttribute *)[theIdAttr objectAtIndex:i] valueClass] isSubclassOfClass:[NSString class]]) && ([(MCPAttribute *)[theIdAttr objectAtIndex:i] width] != 0)) { + [theQuery appendFormat:@"(%@ = SUBSTRING(%@ FROM 1 FOR %u))", [(MCPAttribute *)[theIdAttr objectAtIndex:i] externalName], [theConnection quoteObject:[self valueForKey:[(MCPAttribute *)[theIdAttr objectAtIndex:i] name]]], [(MCPAttribute *)[theIdAttr objectAtIndex:i] width]]; + } + else { + [theQuery appendFormat:@"(%@ = %@)", [(MCPAttribute *)[theIdAttr objectAtIndex:i] externalName], [theConnection quoteObject:[self valueForKey:[(MCPAttribute *)[theIdAttr objectAtIndex:i] name]]]]; + } + } + [theQuery appendString:@" ORDER BY "]; + for (i = 0; [theKeyAttr count] != i; ++i) { + if (i) { + [theQuery appendString:@", "]; + } + [theQuery appendString:[(MCPAttribute *)[theKeyAttr objectAtIndex:i] externalName]]; + } + theResult = [theConnection queryString:theQuery]; + if ([theResult numOfRows]) { // the object was found. + NSDictionary *theFirstRow = [theResult fetchRowAsDictionary]; + if ([theResult numOfRows] != 1) { + NSLog(@"in MCPObject -checkDBIdWithConnection: method.... not only one (as expected) but %i results were found, will take the first one."); + [theKeys setObject:[NSNumber numberWithInt:MCPDBReturnMultiple] forKey:@"MCPDBReturnCode"]; + } + else { + [theKeys setObject:[NSNumber numberWithInt:MCPDBReturnOK] forKey:@"MCPDBReturnCode"]; + } + [theKeys addEntriesFromDictionary:theFirstRow]; +// Setting the value of self for the primary key to the one just found. + for (i=0; [theKeyAttr count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [theKeyAttr objectAtIndex:i]; + + [self setValue:[theFirstRow objectForKey:[theAttribute externalName]] forKey:[theAttribute name]]; + } + } + else { // Object not found in the DB. + for (i = 0; [theKeyAttr count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *)[theKeyAttr objectAtIndex:i]; + + if ([theAttribute autoGenerated]) { + [self setValue:[self defaultValueForKey:[theAttribute name]] forKey:[theAttribute name]]; + } + [theKeys setObject:[self valueForKey:[theAttribute name]] forKey:[theAttribute name]]; + } + [theKeys setObject:[NSNumber numberWithInt:MCPDBReturnNone] forKey:@"MCPDBReturnCode"]; +// Setting the value of self for the primary key to the default values. +/* + for (i=0; [theKeyAttr count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [theKeyAttr objectAtIndex:i]; + + [self setValue:[self defaultValueForKey:[theAttribute name]] forKey:[theAttribute name]]; + } +*/ + } + return [NSDictionary dictionaryWithDictionary:theKeys]; +} + + +- (NSDictionary *) saveInDB +/*" First will use the checkDBIdWithConnection method to check if the entry is already in the database. If this is the case, + it will use the updateInDB... method to update the current DB instance. Otherwise it will make an insert into the database + to save the entry, than it makes a select to get the value of autogenerated column, and use them (if necessary) to return + the primary key (as NSDictionary). + + As for checkDBId... the returned dictionary also have a MCPDBReturnCode key with a NSNumber as value containing the + return code of the operation."*/ +{ + MCPConnection *theConnection = [self connection]; + NSDictionary *theCheckReturn = [self checkDBId]; + NSMutableDictionary *theRet; + int theCheckCode = [(NSNumber *)[theCheckReturn objectForKey:@"MCPDBReturnCode"] intValue]; + unsigned i; + unsigned j; + NSMutableString *theQuery = [NSMutableString string]; + NSArray *theAttr = [classDescription attributes]; + + switch (theCheckCode) { + case MCPDBReturnNoIdentity : // For this object the identity is not set... so we try to save. + break; + + case MCPDBReturnOK : // Object already in the DB (found using checkDBId... method). + // NSLog(@"in saveUsingConnection: the entry already existed so will update the one with key : %@", theCheckReturn); + theCheckCode = [self updateInDB]; + theRet = [NSMutableDictionary dictionaryWithDictionary:[self primaryKey]]; + [theRet setObject:[NSNumber numberWithInt:theCheckCode] forKey:@"MCPDBReturnCode"]; + return [NSDictionary dictionaryWithDictionary:theRet]; + break; + + case MCPDBReturnMultiple : // Multiple were found, the first one will be updated. + NSLog(@"Multiple entries found with the same identity ... will update the first one."); + theCheckCode = [self updateInDB]; + theRet = [NSMutableDictionary dictionaryWithDictionary:[self primaryKey]]; + [theRet setObject:[NSNumber numberWithInt:theCheckCode] forKey:@"MCPDBReturnCode"]; + return [NSDictionary dictionaryWithDictionary:theRet]; + break; + + case MCPDBReturnNone : // The entry is not already in the DB, save it... + break; + + case MCPDBReturnNoConnection : // Self does not have a connection. + NSLog(@"Can not save when the connection is not working..."); + break; + + default : // We should not arrive here anyway. + NSLog(@"For some resons we got a unexpected result from checkDBId : %i", theCheckCode); + break; + } + +// Generate the INSERT query: + [theQuery appendFormat:@"INSERT INTO %@ (", [classDescription externalName]]; + j = 0; + for (i=0; [theAttr count] != i; ++i) { + if (! [(MCPAttribute *)[theAttr objectAtIndex:i] autoGenerated]) { + if (j) { + [theQuery appendString:@", "]; + } + [theQuery appendString:[(MCPAttribute *)[theAttr objectAtIndex:i] externalName]]; + ++j; + } + } + [theQuery appendString:@") VALUES ("]; + j = 0; + for (i=0; [theAttr count] != i; ++i) { + if (! [(MCPAttribute *)[theAttr objectAtIndex:i] autoGenerated]) { + if (j) { + [theQuery appendString:@", "]; + } + [theQuery appendString:[theConnection quoteObject:[self valueForKey:[(MCPAttribute *)[theAttr objectAtIndex:i] name]]]]; + ++j; + } + } + [theQuery appendString:@")"]; +// Finished preparing the query. + +// Now we perform the query... + [theConnection queryString:theQuery]; + if (1 != (i = [theConnection affectedRows])) { // More than one row affected ... Should NEVER occure. + NSLog(@"Problem while saving a MCPObject to the database : number of inserted rows is : %i !!!", i); + NSLog(@"Maybe there is an error : %@ ", [theConnection getLastErrorMessage]); + NSLog(@"The class of the object is : %@, and it's description is :\n%@", [self className], [self descriptionWithLocale:nil]); + theCheckCode = (i == 0) ? MCPDBReturnNone : MCPDBReturnMultiple; + } + else { + theCheckCode = MCPDBReturnOK; + } + +// Finally we get the primary key of the inserted object: + if ([classDescription singleIntAutoGenKey]) { + NSString *thePrimKey = [(MCPAttribute *) [[classDescription primaryKeyAttributes] objectAtIndex:0] name]; + + [self setValue:[NSNumber numberWithLongLong:[theConnection insertId]] forKey:thePrimKey]; + } + [self getAutoGenerated]; + theRet = [NSMutableDictionary dictionaryWithDictionary:[self primaryKey]]; + [theRet setObject:[NSNumber numberWithInt:theCheckCode] forKey:@"MCPDBReturnCode"]; + + return [NSDictionary dictionaryWithDictionary:theRet]; +} + + + +- (MCPDBReturnCode) getAutoGenerated +/*" This method will use the Identity attributes of the object to retrieve the autogenerated +attributes from the database. + +If the identity is not defined for this class/entity, then it will try to use the primary key +to get the auto-generated values. + +Obviously this might generate a bug if one of the Identity attributes is autogenerated. +This will create a trouble if some object does not have an identity defined but still contains +some auto-generated attributes. +"*/ +{ + NSMutableArray *theAutoAttr = [NSMutableArray array]; + NSArray *theAttr = [classDescription attributes]; + NSArray *theKeyAttr = [classDescription primaryKeyAttributes]; + unsigned i,j; + NSMutableString *theQuery; + int theCheckCode; + MCPResult *theResult; + NSDictionary *theRow; + MCPConnection *theConnection = [self connection]; + + if (! theConnection) { + return MCPDBReturnNoConnection; + } + + if (0 != [[classDescription identityAttributes] count]) { + theCheckCode = [(NSNumber *)[[self checkDBId] objectForKey:@"MCPDBReturnCode"] intValue]; + if ((MCPDBReturnOK != theCheckCode) && (MCPDBReturnMultiple != theCheckCode)) { + NSLog(@"Unable to get the primary key for the object, will abort now the fetch of autoGenerated attributes (left unchanged)!"); + return theCheckCode; + } + if (MCPDBReturnMultiple == theCheckCode) { + NSLog(@"Will get the autoGenerated values for the first entry...."); + } + } + j = 0; + for (i=0; [theAttr count] != i; ++i) { // generate the array with autoGenerated attributes. + if ([(MCPAttribute *)[theAttr objectAtIndex:i] autoGenerated]) { + [theAutoAttr insertObject:[theAttr objectAtIndex:i] atIndex:j]; + ++j; + } + } + if (0 == [theAutoAttr count]) { + return MCPDBReturnOK; + } + +// Make the query: + theQuery = [NSMutableString stringWithString:@"SELECT "]; + for (i=0; [theAutoAttr count] != i; ++i) { + if (i) { + [theQuery appendString:@", "]; + } + [theQuery appendString:[(MCPAttribute *)[theAutoAttr objectAtIndex:i] externalName]]; + } + [theQuery appendFormat:@" FROM %@ WHERE ", [classDescription externalName]]; +// Preparing the Where Clause: + for (i=0; [theKeyAttr count] != i; ++i) { + if (i) { + [theQuery appendString:@" AND "]; + } + [theQuery appendFormat:@"( %@ = %@ )", [(MCPAttribute*)[theKeyAttr objectAtIndex:i] externalName], [theConnection quoteObject:[self valueForKey:[(MCPAttribute*)[theKeyAttr objectAtIndex:i] name]]]]; + } +// Query is ready: + theResult = [theConnection queryString:theQuery]; + if (! theResult) { + NSLog(@"While fetching the auto-generated part of the object, got an error -a nil MCPResult-. Will report that as a MCPDBReturnNone."); + return MCPDBReturnNone; + } + if (0 == [theResult numOfRows]) { // The entry was not found in the DB. + if (0 == [[classDescription identityAttributes] count]) { + NSLog(@"It seems that the object is not in the DB... but the identity is not defined so there might be a trouble with having the proper ID."); + } + return MCPDBReturnNone; + } +// Getting the values from the DB select: + theRow = [theResult fetchRowAsDictionary]; + for (i=0; [theAutoAttr count] != i; ++i) { + [self setValue:[theRow objectForKey:[(MCPAttribute*)[theAutoAttr objectAtIndex:i] externalName]] forKey:[(MCPAttribute*)[theAutoAttr objectAtIndex:i] name]]; + } +// Returning the proper value: + if (1 != [theResult numOfRows]) { + NSLog(@"Multiple entries (or none : %llu), got the values for the first one...", [theResult numOfRows]); + return MCPDBReturnMultiple; + } + return MCPDBReturnOK; +} + + +- (MCPDBReturnCode) updateInDB +/*"This method will use the primary key value held by the object to modify the object that is saved in the DB. + + (NO YET IMPLEMENTED) If the primary key is not complete (some of the attributes are to null or 0) and the identityAttributes + (from class description) is not an empty list then it will perform a checkDBId... to try to retrieve the + missing part of the primary key. + "*/ +{ + NSArray *theAttributes = [classDescription attributes]; + NSArray *thePrimKeyAttributes = [classDescription primaryKeyAttributes]; + NSMutableString *theQuery = [NSMutableString string]; + int theCheckCode; + unsigned i, j; + MCPConnection *theConnection = [self connection]; + + if (! theConnection) { + return MCPDBReturnNoConnection; + } +// Generate the query + [theQuery appendFormat:@"UPDATE %@ SET ", [classDescription externalName]]; + // Prepare the value key pairs: + j=0; + for (i=0 ; [theAttributes count] != i; ++i) { + id theValue = [self valueForKey:[(MCPAttribute *)[theAttributes objectAtIndex:i] name]]; + + if ((theValue) && (! [(MCPAttribute *)[theAttributes objectAtIndex:i] autoGenerated])) { + if (j) { + [theQuery appendString:@", "]; + } + [theQuery appendFormat:@"%@ = %@ ", [(MCPAttribute *)[theAttributes objectAtIndex:i] externalName], [theConnection quoteObject:theValue]]; + ++j; + } + } + if (0 == j) { // Nothing has to be updated in this entry.... + NSLog(@"Tried to update a row from an object that does NOT contain any information!!"); + return MCPDBReturnNone; + } + // Prepare the WHERE clause: + [theQuery appendFormat:@" WHERE "]; + j = 0; + for (i=0; [thePrimKeyAttributes count] != i; ++i) { + id theValue = [self valueForKey:[(MCPAttribute *)[thePrimKeyAttributes objectAtIndex:i] name]]; + + if (j) { + [theQuery appendString:@" AND "]; + } + if (theValue) { + [theQuery appendFormat:@"%@ = %@", [(MCPAttribute *)[thePrimKeyAttributes objectAtIndex:i] externalName], [theConnection quoteObject:theValue]]; + ++j; + } + } +// Perform the update: + if (j == [thePrimKeyAttributes count]) { + [theConnection queryString:theQuery]; + i = [theConnection affectedRows]; + switch (i) { + case 0 : + theCheckCode = MCPDBReturnNone; + break; + case 1 : + theCheckCode = MCPDBReturnOK; + break; + default : + theCheckCode = MCPDBReturnMultiple; + break; + } + } + else { + NSDictionary *theKeyCheck = [self checkDBId]; + int theIdCheckCode = [(NSNumber *) [theKeyCheck objectForKey:@"MCPDBReturnCode"] intValue]; + + if (MCPDBReturnOK == theIdCheckCode) { + theCheckCode = [self updateInDB]; + } + else { + theCheckCode = MCPDBReturnNoKey; + } + } +// Return proper code: + return theCheckCode; +} + + +- (MCPDBReturnCode) deleteInDB +/*" Uses the connection to delete the object from the DB. In this process the autoGenerated attributes are set to null. + If the primary key is not completely set, this method calls checkDBId... method to try to complete it + (if the identityAttributes of the class description is not empty)."*/ +{ + NSArray *theKeyAttributes = [classDescription primaryKeyAttributes]; + NSArray *theAttributes = [classDescription attributes]; + unsigned i; +// NSMutableDictionary *theKeyValue = [NSMutableDictionary dictionary]; + int theCheckCode = MCPDBReturnOK; + MCPConnection *theConnection = [self connection]; + + if (! theConnection) { + return MCPDBReturnNoConnection; + } +// Get the value of the primary key: + for (i=0; [theKeyAttributes count] != i; ++i) { + id theValue = [self valueForKey:[(MCPAttribute*) [theKeyAttributes objectAtIndex:i] name]]; + if ((! theValue) || ([theValue isKindOfClass:[NSNumber class]] && (0 == [theValue intValue]))) { + theCheckCode = MCPDBReturnIncompleteKey; + break; + } + } +// If incomplete, try to find the rest, and perform again: + if (MCPDBReturnIncompleteKey == theCheckCode) { + if (MCPDBReturnOK == [(NSNumber *)[[self checkDBId] objectForKey:@"MCPDBReturnCode"] intValue]) { + return [self deleteInDB]; + } + else { + return MCPDBReturnIncompleteKey; + } + } + +// Perform the deletion from the DB: + theCheckCode = [[self class] deleteInDBUsingConnection:theConnection withId:self]; + +// set the auto-generated attributes to proper values: + for (i=0 ; [theAttributes count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [theAttributes objectAtIndex:i]; + if ([theAttribute autoGenerated]) { +// [self setValue:NULL forKey:[theAttribute name]]; + [self setValue:[self defaultValueForKey:[theAttribute name]] forKey:[theAttribute name]]; + } + } + +// Finished... just have to return the proper value: + return theCheckCode; +} + + ++ (MCPDBReturnCode) deleteInDBUsingConnection:(MCPConnection *) iConnection withId:(id) iId +/*" Uses the connection to remove from the DB the entry corresponding to the primary key id given by iId. + If any part of the primary key is missing, nothing is done."*/ +{ + NSMutableString *theQuery = [NSMutableString string]; + MCPClassDescription *theClassDescription = (MCPClassDescription *) [NSClassDescription classDescriptionForClass:[self class]]; + NSArray *theKeyAttributes = [theClassDescription primaryKeyAttributes]; + unsigned i; + + if (! [iConnection checkConnection]) { + return MCPDBReturnNoConnection; + } +// Generate the query: + [theQuery appendFormat:@"DELETE FROM %@ WHERE ", [theClassDescription externalName]]; +// Prepare the WHERE STATEMENT: + for (i=0; [theKeyAttributes count] != i; ++i) { + if (i) { + [theQuery appendString:@" AND "]; + } +// [theQuery appendFormat:@"( %@ = %@ )", [(MCPAttribute*)[theKeyAttributes objectAtIndex:i] externalName], [iConnection quoteObject:[iId objectForKey:[(MCPAttribute*)[theKeyAttributes objectAtIndex:i] name]]]]; + [theQuery appendFormat:@"( %@ = %@ )", [(MCPAttribute*)[theKeyAttributes objectAtIndex:i] externalName], [iConnection quoteObject:[iId valueForKey:[(MCPAttribute*)[theKeyAttributes objectAtIndex:i] name]]]]; + } +// Perform the query: + [iConnection queryString:theQuery]; +// Return the proper code: + i = [iConnection affectedRows]; + + if (1 < i) { + return MCPDBReturnMultiple; + } + if (0 == i) { + return MCPDBReturnNone; + } + return MCPDBReturnOK; +} + + +#pragma mark Handling realtions +- (id) getTargetOfRelation:(MCPRelation *) iRelation +/*" This method is using the information from iRelation to fetch the targe of the relation in the DB. + +If iRelation is flagged as a to-one, then the return type is the type of the target object (value might be nil, if the target was not found). +If iRelation is flagged as a to-many, then the return type is NSArray (and might be empty if no target were found). + +In any case this method is first checking that iRelation is starting from the class of self, and that self is connected to the DB. +Also the returned object is ALWAYS autoreleased before being returned. +"*/ +{ + NSMutableString *query; + MCPClassDescription *destinationDesc; + NSArray *joins; + NSArray *keys; + NSArray *ids; + MCPResult *result; + unsigned int i; + NSDictionary *theRow; + id theRet; + + if (! iRelation) { + NSLog(@"Tried to get the target of a relation... but the relation object is nil"); + } + else { +// NSLog(@"Trying to get the target of the realtion : %@", [iRelation descriptionWithLocale:nil]); + } + if ((! [connection isConnected]) || (! [classDescription isEqual:[iRelation origin]])) { // Error condition. + return nil; + } +// Generating the query: + destinationDesc = [iRelation destination]; + keys = [destinationDesc primaryKeyAttributes]; + joins = [iRelation joins]; + ids = [destinationDesc identityAttributes]; + query = [[NSMutableString alloc] initWithString:@"SELECT "]; + for (i=0; [keys count] != i; ++i) { + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[keys objectAtIndex:i] externalName]]; + } + [query appendFormat:@" FROM %@ WHERE ", [destinationDesc externalName]]; + for (i=0; [joins count] != i; ++i) { + MCPJoin *theJoin = (MCPJoin *)[joins objectAtIndex:i]; + + if (i) { + [query appendString:@" and "]; + } + [query appendFormat:@"(%@ = %@)", [[theJoin destination] externalName], [connection quoteObject:[self valueForKey:[[theJoin origin] name]]]]; + } +// NSLog(@"in -[MCPObject getTargetOfRelation:%@]; query is %@...", [iRelation descriptionWithLocale:nil], query); + [query appendString:[self orderSQLForClassDescription:destinationDesc]]; +// NSLog(@"in -[MCPObject getTargetOfRelation:%@]; query is %@...", [iRelation descriptionWithLocale:nil], query); +/* + [query appendString:@" ORDER BY "]; + for (i=0; [ids count] != i; ++i) { // Generating the order : + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[ids objectAtIndex:i] externalName]]; + } +*/ + result = [connection queryString:query]; + [query release]; + +// Getting the results in proper objects: + if ([iRelation isToMany]) { // To-Many relation + NSMutableArray *theArrayRet = [[NSMutableArray alloc] init]; + + theRet = theArrayRet; + while (theRow = [result fetchRowAsDictionary]) { + MCPObject *theTarget = [[[destinationDesc representedClass] alloc] init]; + + [theTarget setPrimaryKey:theRow andFetchFromDB:connection]; + [theArrayRet insertObject:theTarget atIndex:[theArrayRet count]]; + [theTarget release]; + } + } + else { // To-One relation + theRow = [result fetchRowAsDictionary]; + if (theRow) { + theRet = [[[destinationDesc representedClass] alloc] init]; + [theRet setPrimaryKey:theRow andFetchFromDB:connection]; + } + else { + theRet = nil; + } + } + [theRet autorelease]; + return theRet; +} + +- (id) getTargetOfRelationNamed:(NSString *) iRelationName +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self getTargetOfRelation:theRelation] : nil; +} + +- (MCPDBReturnCode) setTarget:(id) iTarget forRelation:(MCPRelation *) iRelation +/*" This method will modify the DB content so that the corresponding relation is deleted. + If any value corresping to an attribute of self is modified in the DB, then it will update all attributes of self to reflect DB status. + +Obviuosly this method is taking care of only to-one relation. It will hence check that iRelation is a to-one relation starting from self. +Finally, you can use setTarget:nil forRelation:... to 'delete' a previously establlished relation (if the model and DB permit it)."*/ +{ + unsigned int i; + MCPObject *oldTarget; + + if (! [connection isConnected]) { + return MCPDBReturnNoConnection; + } + if (! [classDescription isEqual:[iRelation origin]]) { + return MCPDBReturnWrongRelationOrigin; + } + if ([iRelation isToMany]) { + return MCPDBReturnWrongRelationCardinality; + } + + oldTarget = [self getTargetOfRelation:iRelation]; + if ((oldTarget) && ([iTarget isEqual:oldTarget])) { // No need to change the relation's target. + return MCPDBReturnOK; + } + for (i=0; [iRelation countOfJoins] != i; ++i) { + MCPJoin *theJoin = [iRelation objectInJoinsAtIndex:i]; + + if (([[theJoin origin] isPartOfKey]) && (![[theJoin destination] isPartOfKey])) { // Will change the destination... + [iTarget setValue:[self valueForKey:[[theJoin origin] name]] forKey:[[theJoin destination] name]]; + [oldTarget setValue:[oldTarget defaultValueForKey:[[theJoin destination] name]] forKey:[[theJoin destination] name]]; + } + else { // Will change the origin + [self setValue:[iTarget valueForKey:[[theJoin destination] name]] forKey:[[theJoin origin] name]]; + } + } + if ([iRelation ownsDestination]) { + [oldTarget deleteInDB]; + } + return MCPDBReturnOK; +} + +- (MCPDBReturnCode) setTarget:(id) iTarget forRelationNamed:(NSString *) iRelationName +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self setTarget:iTarget forRelation:theRelation] : MCPDBReturnNoSuchRelation; +} + +- (unsigned int) countTargetForRelation:(MCPRelation *) iRelation +{ + NSMutableString *theQuery; + unsigned int i; + NSArray *theJoinArray; + MCPResult *theResult; + NSDictionary *theRow; + + if ((! [connection isConnected]) || (! [classDescription isEqual:[iRelation origin]])){ + return 0; + } + theJoinArray = [iRelation joins]; + theQuery = [[NSMutableString alloc] initWithFormat:@"SELECT COUNT(*) FROM %@ WHERE ", [[iRelation destination] externalName]]; + for (i=0; [theJoinArray count] != i; ++i) { + MCPJoin *theJoin = (MCPJoin *)[theJoinArray objectAtIndex:i]; + if (i) { + [theQuery appendString:@" AND "]; + } + [theQuery appendFormat:@"( %@ = %@ )", [[theJoin destination] externalName], [connection quoteObject:[self valueForKey:[[theJoin origin] name]]]]; + } + theResult = [connection queryString:theQuery]; + [theQuery release]; + theRow = [theResult fetchRowAsDictionary]; + return [(NSNumber *)[theRow objectForKey:@"COUNT(*)"] unsignedIntValue]; +} + +- (unsigned int) countTargetForRelationNamed:(NSString *) iRelationName +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self countTargetForRelation:theRelation] : 0; +} + + +- (MCPObject *) getTargetOfRelation:(MCPRelation *) iRelation atIndex:(unsigned int) iIndex +/*" This method will return the specific object which is the iIndex'th object of th relation (after ordering the object using the destination identity). + +This method (like other assuming order in the relation targets) will be doubtfull if the class does NOT have a single identity attribute."*/ +{ + NSMutableString *query; + MCPClassDescription *destinationDesc; + NSArray *joins; + NSArray *keys; + NSArray *ids; + MCPResult *result; + unsigned int i; + NSDictionary *theRow; + MCPObject *theRet; + + if (! iRelation) { + NSLog(@"Tried to get the target of a relation... but the relation object is nil"); + } + else { +// NSLog(@"Trying to get the target of the realtion : %@", [iRelation descriptionWithLocale:nil]); + } + if ((! [connection isConnected]) || (! [classDescription isEqual:[iRelation origin]])) { // Error condition. + return nil; + } + if (! [iRelation isToMany]) { + NSLog(@"Tried to use the -[MCPObject getTargetOfRelation:atIndex:] on a to-one relation... this does NOT works!!! You should use -[MCPObject getTargetOfRelation:] instead!!!"); + return [self getTargetOfRelation:iRelation]; + } +// Generating the query: + destinationDesc = [iRelation destination]; + keys = [destinationDesc primaryKeyAttributes]; + joins = [iRelation joins]; + ids = [destinationDesc identityAttributes]; + query = [[NSMutableString alloc] initWithString:@"SELECT "]; + for (i=0; [keys count] != i; ++i) { + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[keys objectAtIndex:i] externalName]]; + } + [query appendFormat:@" FROM %@ WHERE ", [destinationDesc externalName]]; + for (i=0; [joins count] != i; ++i) { + MCPJoin *theJoin = (MCPJoin *)[joins objectAtIndex:i]; + + if (i) { + [query appendString:@" and "]; + } + [query appendFormat:@"(%@ = %@)", [[theJoin destination] externalName], [connection quoteObject:[self valueForKey:[[theJoin origin] name]]]]; + } + [query appendString:[self orderSQLForClassDescription:destinationDesc]]; +/* + [query appendString:@" ORDER BY "]; + for (i=0; [ids count] != i; ++i) { // Generating the order : + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[ids objectAtIndex:i] externalName]]; + } +*/ + [query appendFormat:@" LIMIT 1 OFFSET %u", iIndex]; + result = [connection queryString:query]; + [query release]; + +// Getting the results in proper objects: + theRow = [result fetchRowAsDictionary]; + if (theRow) { + theRet = (MCPObject *)[[[destinationDesc representedClass] alloc] init]; + [theRet setPrimaryKey:theRow andFetchFromDB:connection]; + [theRet autorelease]; + } + else { + theRet = nil; + } + return theRet; +} + +- (MCPObject *) getTargetOfRelationNamed:(NSString *) iRelationName atIndex:(unsigned int) iIndex +/*"This is the equivalent of the getTargetOfRelation:atIndex:, but giving a relation name instead of the MCPRelation object itself."*/ +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self getTargetOfRelation:theRelation atIndex:iIndex]: nil; +} + +- (MCPDBReturnCode) addTarget:(MCPObject *) iTarget toRelation:(MCPRelation *) iRelation +/*" This method will modify the DB content so that the corresponding relation is added. +If any value corresping to an attribute of self i modified in the DB, then it will update all attributes of self to reflect DB status. + +Obviuosly this method is taking care of only to-many relation. It will hence check that iRelation is a to-many relation starting from self."*/ +{ + NSArray *joins; + unsigned int i; + NSDictionary *saveReturn; + + if (! [connection isConnected]) { + return MCPDBReturnNoConnection; + } + if (! [classDescription isEqual:[iRelation origin]]) { + return MCPDBReturnWrongRelationOrigin; + } + if (! [iRelation isToMany]) { + return MCPDBReturnWrongRelationCardinality; + } + joins = [iRelation joins]; + for (i=0; [joins count] != i; ++i) { // Will change only values of iTarget, because it is a to-many relation. + MCPJoin *join = (MCPJoin *)[joins objectAtIndex:i]; + + [iTarget setValue:[self valueForKey:[[join origin] name]] forKey:[[join destination] name]]; + } + if (! [[iTarget connection] isConnected]) { + [iTarget setConnection:connection]; + } + saveReturn = [iTarget saveInDB]; + return (MCPDBReturnCode)[(NSNumber *)[saveReturn objectForKey:@"MCPDBReturnCode"] unsignedIntValue]; +} + +- (MCPDBReturnCode) addTarget:(MCPObject *) iTarget toRelationNamed:(NSString *) iRelationName +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self addTarget:iTarget toRelation:theRelation] : MCPDBReturnNoSuchRelation; +} + +- (MCPDBReturnCode) removeTarget:(MCPObject *) iTarget toRelation:(MCPRelation *) iRelation +/*" This method will modify the DB content so that the corresponding relation is removed. +If any value corresping to an attribute of self i modified in the DB, then it will update all attributes of self to reflect DB status. + +Obviuosly this method is taking care of only to-many relation. It will hence check that iRelation is a to-many relation starting from self."*/ +{ + NSArray *joins; + unsigned int i; +// NSDictionary *saveReturn; + BOOL targetIsTarget = YES; + MCPDBReturnCode returnCode; + + if (! [connection isConnected]) { + return MCPDBReturnNoConnection; + } + if (! [classDescription isEqual:[iRelation origin]]) { + return MCPDBReturnWrongRelationOrigin; + } + if (! [iRelation isToMany]) { + return MCPDBReturnWrongRelationCardinality; + } + joins = [iRelation joins]; + for (i=0; [joins count] != i; ++i) { + MCPJoin *join = (MCPJoin *)[joins objectAtIndex:i]; + targetIsTarget = targetIsTarget && [[iTarget valueForKey:[[join destination] name]] isEqual:[self valueForKey:[[join origin] name]]]; + } + if (! targetIsTarget) { + return MCPDBReturnNotTarget; + } + if ([iRelation ownsDestination]) { // just delete the target from the DB. + returnCode = [iTarget deleteInDB]; + } + for (i=0; [joins count] != i; ++i) { // Put all the destination to default... + MCPJoin *join = (MCPJoin *)[joins objectAtIndex:i]; + + [iTarget setValue:[[join destination] defaultValue] forKey:[[join destination] name]]; + } + if (! [iRelation ownsDestination]) { + returnCode = [(NSNumber *)[[iTarget saveInDB] objectForKey:@"MCPDBReturnCode"] unsignedIntValue]; + } + return returnCode; +} + +- (MCPDBReturnCode) removeTarget:(MCPObject *) iTarget toRelationNamed:(NSString *) iRelationName +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self removeTarget:iTarget toRelation:theRelation] : MCPDBReturnNoSuchRelation; +} + +- (MCPDBReturnCode) removeTargetToRelation:(MCPRelation *) iRelation atIndex:(unsigned int) iIndex +/*" This method will use an index t first query the object that it should remove from the relation, then uses the -[MCPObject removeTarget:toRelation:] + method to remove the object from the relation. If the index is out of bound (the returned object is nil), it will return MCPDBReturnNone, to signal + there was no object with this index in the relation."*/ +{ + MCPObject *target; + + if (! [connection isConnected]) { + return MCPDBReturnNoConnection; + } + if (! [classDescription isEqual:[iRelation origin]]) { + return MCPDBReturnWrongRelationOrigin; + } + if (! [iRelation isToMany]) { + return MCPDBReturnWrongRelationCardinality; + } + target = [self getTargetOfRelation:iRelation atIndex:iIndex]; + if (target) { + return [self removeTarget:target toRelation:iRelation]; + } + else { + return MCPDBReturnNone; + } +} + +- (MCPDBReturnCode) removeTargetToRelationNamed:(NSString *) iRelationName atIndex:(unsigned int) iIndex +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self removeTargetToRelation:theRelation atIndex:iIndex] : MCPDBReturnNoSuchRelation; +} + +- (unsigned int) indexOfTarget:(MCPObject *) iTarget inRelation:(MCPRelation *) iRelation +/*" Returns the index of the target object within the relation. Return NSNotFound if the objkect is NOT in the relation!!"*/ +{ + NSMutableString *query; + NSArray *joins; + NSArray *keys; + NSArray *ids; + unsigned int i; + MCPResult *result; + NSDictionary *row; + NSMutableDictionary *targetKey; + BOOL targetIsTarget = YES; + + if ((! [connection isConnected]) || (! [classDescription isEqual:[iRelation origin]]) || (! [iRelation isToMany])) { // Checking the realtion object. + return NSNotFound; + } + query = [[NSMutableString alloc] initWithString:@"SELECT "]; + joins = [iRelation joins]; +// keys = [[iRelation destination] attributeKeys]; + keys = [[iRelation destination] primaryKeyAttributes]; + ids = [[iRelation destination] identityAttributes]; + for (i=0; [keys count] != i; ++i) { + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[keys objectAtIndex:i] externalName]]; + } + [query appendFormat:@"FROM %@ WHERE ", [[iRelation destination] externalName]]; + for (i=0; [joins count] != i; ++i) { + MCPJoin *join = (MCPJoin *)[joins objectAtIndex:i]; + + targetIsTarget = targetIsTarget && [[iTarget valueForKey:[[join destination] name]] isEqual:[self valueForKey:[[join origin] name]]]; + if (i) { + [query appendString:@" AND "]; + } + [query appendFormat:@"( %@ = %@ )", [[join destination] externalName], [connection quoteObject:[self valueForKey:[[join origin] name]]]]; + } + if (! targetIsTarget) { // Checking that iTarget belongs to the relation. + [query release]; + return NSNotFound; + } + [query appendString:[self orderSQLForClassDescription:[iRelation destination]]]; +/* + [query appendString:@" ORDER BY "]; + for (i=0; [ids count] != i; ++i) { + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[ids objectAtIndex:i] externalName]]; + } +*/ + targetKey = [[NSMutableDictionary alloc] init]; + for (i=0; [keys count] != i; ++i) { // Setting the targetKey to the row that should gives the target. + MCPAttribute *attribute = (MCPAttribute *)[keys objectAtIndex:i]; + + [targetKey setObject:[iTarget valueForKey:[attribute name]] forKey:[attribute externalName]]; + } + result = [connection queryString:query]; + [query release]; + i = 0; + while (row = [result fetchRowAsDictionary]) { + if ([targetKey isEqualToDictionary:row]) { // We have found the proper object return i (after cleaning up); + [targetKey release]; + return i; + } + ++i; + } + return NSNotFound; +} + +- (unsigned int) indexOfTarget:(MCPObject *) iTarget inRelationNamed:(NSString *) iRelationName +/*" The equivalent of -[MCPObject indexOfTarget:inRelation:] but using relation name instead of a MCPRelation +object. Indeed after getting the MCPRelation object corresponding to the name, this method will only return +the result of the corresponding call to -[MCPObject indexOfTarget:inRelation:]."*/ +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self indexOfTarget:iTarget inRelation:theRelation] : NSNotFound; +} + +#pragma mark Utility methods +- (id) defaultValueForKey:(NSString *) iKey +/*" This method will return the default value (object) for the given key."*/ +{ + MCPAttribute *theAttribute = [classDescription attributeWithName:iKey]; + + if ([theAttribute allowsNull]) { +// return [NSNull null]; + return nil; + } + else { + Class theAttrClass = [theAttribute valueClass]; + id theRet; + + if ([theAttrClass isSubclassOfClass:[NSNumber class]]) { + return [NSNumber numberWithInt:0]; + } + theRet = [[[theAttrClass alloc] init] autorelease]; + if (nil == theRet) { + NSLog(@"in MCPObject defaultValueForKey:%@ , for object of class %@, (attribute of class %@) the return value will be nil!!!", iKey, [self className], theAttrClass); + } + return theRet; + } +} + + +- (NSDictionary *) primaryKey +/*" Returns a dictionary with the values of the primary key. "*/ +{ + NSMutableDictionary *theRet = [NSMutableDictionary dictionary]; + NSArray *theIdAttr = [classDescription primaryKeyAttributes]; + unsigned i; + + for (i=0; [theIdAttr count] != i; ++i) { + NSString *theKey = [(MCPAttribute *)[theIdAttr objectAtIndex:i] name]; + + if ([self valueForKey:theKey]) { + [theRet setObject:[self valueForKey:theKey] forKey:theKey]; + } + else { + [theRet setObject:[NSNull null] forKey:theKey]; + } + } + return [NSDictionary dictionaryWithDictionary:theRet]; +} + +#pragma mark Testing equality (VERY important for relation management) +- (BOOL) isEqual:(id) iObject +{ + NSArray *theIdAttr; + unsigned int i; + BOOL theRet; + + if (self == iObject) { + return YES; + } + if ([self class] != [iObject class]) { + return NO; + } + theIdAttr = [classDescription identityAttributes]; + for (i = 0; [theIdAttr count] != i; ++i) { + MCPAttribute *theAttr = [theIdAttr objectAtIndex:i]; + + theRet = theRet && [[self valueForKey:[theAttr name]] isEqual:[iObject valueForKey:[theAttr name]]]; + } + return theRet; +} + + +#pragma mark Output +- (NSString *) description +{ + return [self descriptionWithLocale:nil]; +} + +- (NSString *) descriptionWithLocale:(NSDictionary *) locale +{ + NSMutableString *theOutput = [NSMutableString string]; + unsigned int i; + NSArray *theAttributes = [classDescription attributes]; + BOOL trunc = [MCPConnection truncateLongField]; + + [theOutput appendFormat:@"MCPObject subclass : %@\n", [self className]]; + for (i=0; [theAttributes count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [theAttributes objectAtIndex:i]; + id theValue = [self valueForKey:[theAttribute name]]; + + if (trunc) { + if (([theValue isKindOfClass:[NSString class]]) && (kLengthOfTruncationForLog < [(NSString *)theValue length])) { + theValue = [theValue substringToIndex:kLengthOfTruncationForLog]; + } + else if (([theValue isKindOfClass:[NSData class]]) && (kLengthOfTruncationForLog < [(NSData *)theValue length])) { + theValue = [NSData dataWithBytes:[theValue bytes] length:kLengthOfTruncationForLog]; + } + } + [theOutput appendFormat:@"\tAttribute %u : name = %@, value = %@\n", i, [theAttribute name], theValue]; + } + [theOutput appendString:@"\n"]; + return theOutput; +} + +#pragma mark Ordering the array for relations +- (NSString *) orderSQLForClassDescription:(MCPClassDescription *) iClassDescription +{ + NSMutableArray *theAttributes = [[NSMutableArray alloc] initWithArray:[iClassDescription identityAttributes]]; + NSMutableString *theReturn = [NSMutableString string]; + unsigned int i; + + for (i = 0; [[iClassDescription primaryKeyAttributes] count] != i; ++i) { + [theAttributes insertObject:[[iClassDescription primaryKeyAttributes] objectAtIndex:i] atIndex:[theAttributes count]]; + } + for (i = 0; [theAttributes count] != i; ++i) { + if (i) { + [theReturn appendString:@", "]; + } + else { + [theReturn appendString:@" ORDER BY "]; + } + [theReturn appendString:[(MCPAttribute *)[theAttributes objectAtIndex:i] externalName]]; + } + return theReturn; +} + +#pragma mark Anti-crash method... +- (void) setNilValueForKey:(NSString *) iKey +{ + NSLog(@"Try to set %@ to nil .... not possible, will set it to zero instead...", iKey); + [self setValue:[NSNumber numberWithInt:0] forKey:iKey]; +} + +@end |