// // $Id$ // // 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 // More info at #import "MCPObject.h" #import #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) { NSUInteger 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]; NSUInteger 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; NSUInteger 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]]; NSUInteger 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]; NSUInteger 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; NSUInteger i; if (! theConnection) { return [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:MCPDBReturnNoConnection], @"MCPDBReturnCode", nil]; } if (! [theKeyAttr count]) { // There is no primary key for this object. [theKeys setObject:[NSNumber numberWithInteger:MCPDBReturnNoKey] forKey:@"MCPDBReturnCode"]; return [NSDictionary dictionaryWithDictionary:theKeys]; } if (! [theIdAttr count]) { // Identity is not defined for this object. [theKeys setObject:[NSNumber numberWithInteger: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 %ld))", [(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 %ld results were found, will take the first one."); [theKeys setObject:[NSNumber numberWithInteger:MCPDBReturnMultiple] forKey:@"MCPDBReturnCode"]; } else { [theKeys setObject:[NSNumber numberWithInteger: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 numberWithInteger: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; NSInteger theCheckCode = [(NSNumber *)[theCheckReturn objectForKey:@"MCPDBReturnCode"] integerValue]; NSUInteger i; NSUInteger 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 numberWithInteger: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 numberWithInteger: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 : %ld", 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 : %ld !!!", 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 numberWithInteger: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]; NSUInteger i,j; NSMutableString *theQuery; NSInteger theCheckCode; MCPResult *theResult; NSDictionary *theRow; MCPConnection *theConnection = [self connection]; if (! theConnection) { return MCPDBReturnNoConnection; } if (0 != [[classDescription identityAttributes] count]) { theCheckCode = [(NSNumber *)[[self checkDBId] objectForKey:@"MCPDBReturnCode"] integerValue]; 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]; NSInteger theCheckCode; NSUInteger 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]; NSInteger theIdCheckCode = [(NSNumber *) [theKeyCheck objectForKey:@"MCPDBReturnCode"] integerValue]; 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]; NSUInteger i; // NSMutableDictionary *theKeyValue = [NSMutableDictionary dictionary]; NSInteger 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 integerValue]))) { theCheckCode = MCPDBReturnIncompleteKey; break; } } // If incomplete, try to find the rest, and perform again: if (MCPDBReturnIncompleteKey == theCheckCode) { if (MCPDBReturnOK == [(NSNumber *)[[self checkDBId] objectForKey:@"MCPDBReturnCode"] integerValue]) { 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]; NSUInteger 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; NSUInteger 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)."*/ { NSUInteger 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; } - (NSUInteger) countTargetForRelation:(MCPRelation *) iRelation { NSMutableString *theQuery; NSUInteger 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(1) 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(1)"] unsignedIntegerValue]; } - (NSUInteger) countTargetForRelationNamed:(NSString *) iRelationName { MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; return (theRelation) ? [self countTargetForRelation:theRelation] : 0; } - (MCPObject *) getTargetOfRelation:(MCPRelation *) iRelation atIndex:(NSUInteger) 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; NSUInteger 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 %ld", 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:(NSUInteger) 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; NSUInteger 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"] unsignedIntegerValue]; } - (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; NSUInteger 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"] unsignedIntegerValue]; } 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:(NSUInteger) 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:(NSUInteger) iIndex { MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; return (theRelation) ? [self removeTargetToRelation:theRelation atIndex:iIndex] : MCPDBReturnNoSuchRelation; } - (NSUInteger) 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; NSUInteger 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; } - (NSUInteger) 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 numberWithInteger: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]; NSUInteger 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; NSUInteger 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]; NSUInteger 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 %ld : 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]; NSUInteger 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 numberWithInteger:0] forKey:iKey]; } @end