aboutsummaryrefslogtreecommitdiffstats
path: root/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m
diff options
context:
space:
mode:
Diffstat (limited to 'Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m')
-rw-r--r--Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m1337
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