diff options
author | stuconnolly <stuart02@gmail.com> | 2009-07-21 16:47:11 +0000 |
---|---|---|
committer | stuconnolly <stuart02@gmail.com> | 2009-07-21 16:47:11 +0000 |
commit | 8b8f3e6cea540b17262aadf6d97a8ad28fe41c03 (patch) | |
tree | ad6bb7f53b03924aa24d0cf5822a27b3bd453592 /Frameworks/MCPKit/MCPEntrepriseKit | |
parent | 383863f98dfc488db0181e01d39da1bb025d421b (diff) | |
download | sequelpro-8b8f3e6cea540b17262aadf6d97a8ad28fe41c03.tar.gz sequelpro-8b8f3e6cea540b17262aadf6d97a8ad28fe41c03.tar.bz2 sequelpro-8b8f3e6cea540b17262aadf6d97a8ad28fe41c03.zip |
Merge framework integration branch back to trunk. Summary of changes:
- Includes all custom code from subclasses CMMCPConnection and CMMCPResult, meaning they have subsequently been removed from the project.
- All previous Sequel Pro specific code in the above subclasses has been removed in favour of the delegate (currently set to TableDocumet) informing the framework of such information.
- All references to CMMCPConnection and CMMCPResult have subsequently been changed to MCPConnection and MCPResult.
- Framework includes MySQL 5.1.36 client libraries and source headers.
- Framework is now built as a 4-way (32/64 bit, i386/PPC arch) binary.
- All import references to <MCPKit_bundled/MCPKit_bundled.h> have been changed to <MCPKit/MCPKit.h>.
- New script 'build-mysql-client.sh' can be used to build the MySQL client libraries from the MySQL source. See the script's header for a list of available options or run it with no arguments to display it's usage.
Note that there are still a few changes to be made to the framework with regard to removing Sequel Pro specific calls to the delegate. These however can be made later on as they have no effect on functionality and are merely design changes.
Also, note that any future development done on the framework should be made to be as 'generic' as possible, with no Sequel Pro specific references. This should allow the framework to be integrated into another project without the need for SP specific code.
Diffstat (limited to 'Frameworks/MCPKit/MCPEntrepriseKit')
20 files changed, 4155 insertions, 0 deletions
diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.h new file mode 100644 index 00000000..1a474346 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.h @@ -0,0 +1,106 @@ +// +// $Id: MCPAttribute.h 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPAttribute.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/08/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 <Foundation/Foundation.h> + +@class MCPModel; +@class MCPClassDescription; +@class MCPRelation; +@class MCPJoin; + +@interface MCPAttribute : NSObject < NSCoding > +{ +@protected + MCPClassDescription *classDescription; // ClassDescription of which the attribute is attribute + NSString *name; // Name of the attribute (Obj-C side) + Class valueClass; // Class used by the attribute (or NULL if the internal type is not an object) + NSString *internalType; // Name of the class, or type used for the class definition + NSString *externalName; // Name of the corresponding column in the DB + NSString *externalType; // Type used to store the attribute (in the DB) + unsigned int width; // Width (for storing by the DB) + BOOL allowsNull; // Attribute can be null + BOOL autoGenerated; // Attribute is auto generated by the DB + BOOL isPartOfKey; // Attribute is part of theprimary key of the class description + BOOL isPartOfIdentity; // Attribute is part of the idclass description of the class description + BOOL hasAccessor; // Does this attribute have an accessor + id defaultValue; // Default value of the attribute + NSMutableArray *joins; // An array of the joins using this attribute +} + +#pragma mark Class methods ++ (void) initialize; + +#pragma mark Life cycle +- (id) initForClassDescription:(MCPClassDescription *) iClassDescription withName:(NSString *) iName; +- (void) dealloc; + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder; +- (void) encodeWithCoder:(NSCoder *) encoder; + +#pragma mark Setters +- (void) setName:(NSString *) iName; +- (void) setValueClass:(Class) iValueClass; +- (void) setInternalType:(NSString *) iInternalType; +- (void) setExternalType:(NSString *) iExternalType; +- (void) setExternalName:(NSString *) iExternalName; +- (void) setWidth:(unsigned int) iWidth; +- (void) setAllowsNull:(BOOL) iAllowsNull; +- (void) setAutoGenerated:(BOOL) iAutoGenerated; +- (void) setIsPartOfKey:(BOOL) iIsPartOfKey; +- (void) setIsPartOfIdentity:(BOOL) iIsPartOfIdentity; +- (void) setHasAccessor:(BOOL) iHasAccessor; +- (void) setDefaultValue:(id) iDefaultValue; +- (void) insertObject:(MCPJoin *) iJoin inJoinsAtIndex:(unsigned int) index; +- (void) removeObjectFromJoinsAtIndex:(unsigned int) index; +//- (void) addRelation:(MCPRelation *) iRelation; +//- (void) removeRelation:(MCPRelation *) iRelation; + +#pragma mark Getters +- (MCPClassDescription *) classDescription; +- (NSString *) name; +- (Class) valueClass; +- (NSString *) valueClassName; +- (NSString *) internalType; +- (NSString *) externalName; +- (NSString *) externalType; +- (unsigned int) width; +- (BOOL) allowsNull; +- (BOOL) autoGenerated; +- (BOOL) isPartOfKey; +- (BOOL) isPartOfIdentity; +- (BOOL) hasAccessor; +- (id) defaultValue; +- (unsigned int) countOfJoins; +- (MCPJoin *) objectInJoinsAtIndex:(unsigned int) index; +- (unsigned int) indexOfJoinIdenticalTo:(id) iJoin; + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.m new file mode 100644 index 00000000..34b50284 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.m @@ -0,0 +1,466 @@ +// +// $Id: MCPAttribute.m 927 2009-06-24 10:53:07Z stuart02 $ +// +// MCPAttribute.m +// MCPkit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/08/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 "MCPAttribute.h" + +#import "MCPEntrepriseNotifications.h" + +#import "MCPModel.h" +#import "MCPClassDescription.h" +#import "MCPRelation.h" +#import "MCPJoin.h" + +static NSArray *MCPRecognisedInternalType; + +@interface MCPAttribute (Private) + +- (void)setValueClassName:(NSString *) iClassName; + +@end + +@implementation MCPAttribute + +#pragma mark Class methods ++ (void) initialize +{ + if (self == [MCPAttribute class]) { + [self setVersion:010101]; // Ma.Mi.Re -> MaMiRe + MCPRecognisedInternalType = [[NSArray alloc] initWithObjects:@"NSCalendarDate", @"NSData", @"NSNumber", @"NSString", nil]; + [self setKeys:[NSArray arrayWithObject:@"internalType"] triggerChangeNotificationsForDependentKey:@"valueClassName"]; + [self setKeys:[NSArray arrayWithObject:@"valueClassName"] triggerChangeNotificationsForDependentKey:@"internalType"]; + } + return; +} + + +#pragma mark Life cycle +- (id) initForClassDescription:(MCPClassDescription *) iClassDescription withName:(NSString *) iName +{ + self = [super init]; + { + classDescription = iClassDescription; + [self setName:iName]; +// relations = (NSMutableArray *)(CFArrayCreateMutable (kCFAllocatorDefault, 0, NULL)); + joins = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) dealloc +{ +// NSArray *theRelations; +// unsigned int i; + + [name release]; + [internalType release]; + [externalName release]; + [externalType release]; + [defaultValue release]; +/* + while ([relations count]) { + [(MCPRelation *)[relations objectAtIndex:0] unjoinAttribute:self]; + } +// By now relation should be empty anyway... + [relations release]; + */ + while ([joins count]) { + [[self objectInJoinsAtIndex:0] invalidate]; + } + // By now the joins array should be empty + [joins release]; + [super dealloc]; +} + + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder +{ + self = [super init]; + if ((self) && ([decoder allowsKeyedCoding])) { + NSString *theClassName = [decoder decodeObjectForKey:@"MCPvalueClassName"]; + + classDescription = [decoder decodeObjectForKey:@"MCPclassDescription"]; + [self setName:[decoder decodeObjectForKey:@"MCPname"]]; + if (theClassName) { + [self setValueClass:NSClassFromString(theClassName)]; + } + [self setInternalType:[decoder decodeObjectForKey:@"MCPinternalType"]]; + [self setExternalName:[decoder decodeObjectForKey:@"MCPexternalName"]]; + [self setExternalType:[decoder decodeObjectForKey:@"MCPexternalType"]]; + [self setWidth:(unsigned int)[decoder decodeInt32ForKey:@"MCPwidth"]]; + [self setAllowsNull:[decoder decodeBoolForKey:@"MCPallowsNull"]]; + [self setAutoGenerated:[decoder decodeBoolForKey:@"MCPautoGenerated"]]; + [self setIsPartOfKey:[decoder decodeBoolForKey:@"MCPisPartOfKey"]]; + [self setIsPartOfIdentity:[decoder decodeBoolForKey:@"MCPisPartOfIdentity"]]; + [self setHasAccessor:[decoder decodeBoolForKey:@"MCPhasAccessor"]]; + [self setDefaultValue:[decoder decodeObjectForKey:@"MCPdefaultValue"]]; +// Not sure that the next line is working (getting an array holding weak references), hence doing the thing expelcitly: +// relations = [[decoder decodeObjectForKey:@"MCPrelations"] retain]; +// relations = (NSMutableArray *)(CFArrayCreateMutable (kCFAllocatorDefault, 0, NULL)); +// [relations addObjectsFromArray:[decoder decodeObjectForKey:@"MCPrelations"]]; + joins = [[NSMutableArray alloc] init]; // Will be filled in when the relations are read in. + } + else { + NSLog(@"For some reason, unable to decode MCPAttribute from the coder!!!"); + } +// NSLog(@"MAKING a new object : %@", self); + return self; +} + +- (void) encodeWithCoder:(NSCoder *) encoder +{ + NSString *theValueClassName; + + if (! [encoder allowsKeyedCoding]) { + NSLog(@"In MCPAttribute -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!"); + return; + } +// theValueClassName = (valueClass) ? [valueClass className] : nil; + theValueClassName = (valueClass) ? NSStringFromClass(valueClass) : nil; + [encoder encodeObject:[self classDescription] forKey:@"MCPclassDescription"]; + [encoder encodeObject:[self name] forKey:@"MCPname"]; + if (theValueClassName) { + [encoder encodeObject:theValueClassName forKey:@"MCPvalueClassName"]; + } + [encoder encodeObject:[self internalType] forKey:@"MCPinternalType"]; + [encoder encodeObject:[self externalName] forKey:@"MCPexternalName"]; + [encoder encodeObject:[self externalType] forKey:@"MCPexternalType"]; + [encoder encodeInt32:(int32_t)[self width] forKey:@"MCPwidth"]; + [encoder encodeBool:[self allowsNull] forKey:@"MCPallowsNull"]; + [encoder encodeBool:[self autoGenerated] forKey:@"MCPautoGenerated"]; + [encoder encodeBool:[self isPartOfKey] forKey:@"MCPisPartOfKey"]; + [encoder encodeBool:[self isPartOfIdentity] forKey:@"MCPisPartOfIdentity"]; + [encoder encodeBool:[self hasAccessor] forKey:@"MCPhasAccessor"]; + [encoder encodeObject:[self defaultValue] forKey:@"MCPdefaultValue"]; +// [encoder encodeObject:relations forKey:@"MCPrelation"]; + // We don't have to save the joins here ... the joins are saving there attributes. + // The links are recreated when the joins are decoded. +} + +#pragma mark Setters +- (void) setName:(NSString *) iName +{ + if (iName != name) { + [name release]; + name = [iName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setValueClass:(Class) iValueClass +{ + if (iValueClass != valueClass) { + valueClass = iValueClass; + if (valueClass) { // Not nil : set the internalType accrodingly. + // [internalType release]; + // internalType = [[valueClass className] copy]; +// [self setValue:[NSString stringWithString:[valueClass className]] forKey:@"internalType"]; + [self setValue:[NSString stringWithString:NSStringFromClass(valueClass)] forKey:@"internalType"]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setInternalType:(NSString *) iInternalType +{ + if (iInternalType != internalType) { + [internalType release]; + internalType = [iInternalType retain]; + if ([MCPRecognisedInternalType containsObject:internalType]) { + [self setValueClass:NSClassFromString(internalType)]; +// By itself does NOT provide observers the update. +// but see setKeys:triggerChangeNotificationsForDependentKey... (in +initialize). + } + else { + [self setValueClass:nil]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setExternalType:(NSString *) iExternalType +{ + if (iExternalType != externalType) { + [externalType release]; + externalType = [iExternalType retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setExternalName:(NSString *) iExternalName +{ + if (iExternalName != externalName) { + [externalName release]; + externalName = [iExternalName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setWidth:(unsigned int) iWidth +{ + if (iWidth != width) { + width = iWidth; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setAllowsNull:(BOOL) iAllowsNull +{ + if (iAllowsNull != allowsNull) { + allowsNull = iAllowsNull; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setAutoGenerated:(BOOL) iAutoGenerated +{ + if (iAutoGenerated != autoGenerated) { + autoGenerated = iAutoGenerated; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setIsPartOfKey:(BOOL) iIsPartOfKey +{ + if (iIsPartOfKey != isPartOfKey) { + isPartOfKey = iIsPartOfKey; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setIsPartOfIdentity:(BOOL) iIsPartOfIdentity +{ + if (iIsPartOfIdentity != isPartOfIdentity) { + isPartOfIdentity = iIsPartOfIdentity; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setHasAccessor:(BOOL) iHasAccessor +{ + if (iHasAccessor != hasAccessor) { + hasAccessor = iHasAccessor; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setDefaultValue:(id) iDefaultValue +{ + if (iDefaultValue != defaultValue) { + [defaultValue release]; + defaultValue = [iDefaultValue retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) insertObject:(MCPJoin *) iJoin inJoinsAtIndex:(unsigned int) index +{ + [joins insertObject:iJoin atIndex:index]; +} + +- (void) removeObjectFromJoinsAtIndex:(unsigned int) index +{ + [joins removeObjectAtIndex:index]; +} + +/* +- (void) addRelation:(MCPRelation *) iRelation +{ +// Following implementation make sure that a given relation is only added once... but I don't see the reason for that to be true. + /* if (NSNotFound == [relations indexOfObjectIdenticalTo:iRelation]) { + [relations addObject:iRelation]; + } + *//* + [relations addObject:iRelation]; +} + +- (void) removeRelation:(MCPRelation *) iRelation +{ +// Following implementation needs only one reference to a given relation to be working properly (not true) +// [relations removeObjectIdenticalTo:iRelation]; + unsigned int i; + + i = [relations indexOfObjectIdenticalTo:iRelation]; + if (NSNotFound != i) { + [relations removeObjectAtIndex:i]; + } +// If the relation is there more than once, remove it only once. +} +*/ + +#pragma mark Getters +- (MCPClassDescription *) classDescription +{ + return classDescription; +} + +- (NSString *) name +{ + return name; +} + +- (Class) valueClass +{ + return valueClass; +} + +- (NSString *) valueClassName +{ + return NSStringFromClass(valueClass); +} + +- (NSString *) internalType +{ + return internalType; +} + +- (NSString *) externalName +{ + return externalName; +} + +- (NSString *) externalType +{ + return externalType; +} + +- (unsigned int) width +{ + return width; +} + +- (BOOL) allowsNull +{ + return allowsNull; +} + +- (BOOL) autoGenerated +{ + return autoGenerated; +} + +- (BOOL) isPartOfKey +{ + return isPartOfKey; +} + +- (BOOL) isPartOfIdentity +{ + return isPartOfIdentity; +} + +- (BOOL) hasAccessor +{ + return hasAccessor; +} + +- (id) defaultValue +{ + return defaultValue; +} + +- (unsigned int) countOfJoins +{ + return [joins count]; +} + +- (MCPJoin *) objectInJoinsAtIndex:(unsigned int) index +{ + return (MCPJoin *)((NSNotFound != index) ? [joins objectAtIndex:index] : nil); +} + +- (unsigned int) indexOfJoinIdenticalTo:(id) iJoin +{ + return [joins indexOfObjectIdenticalTo:iJoin]; +} + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject +// Equal to another attribute, if they have the same name and same class description. +// Equal to a string (NSString), if the name of the attribute is equal to the string. +{ + if ([iObject isKindOfClass:[MCPAttribute class]]) { + MCPAttribute *theAttribute = (MCPAttribute *) iObject; + + return ([name isEqualToString:[theAttribute name]]) && ([classDescription isEqual:[theAttribute classDescription]]); + } + if ([iObject isKindOfClass:[NSString class]]) { + return [name isEqualToString:(NSString *)iObject]; + } + return NO; +} + +#pragma mark For debugging the retain counting +- (id) retain +{ + [super retain]; + return self; +} + +- (void) release +{ + [super release]; + return; +} + +@end + +@implementation MCPAttribute (Private) + +- (void)setValueClassName:(NSString *) iClassName +{ + if (NSClassFromString(iClassName) != valueClass) { + [self setValueClass:NSClassFromString(iClassName)]; + } +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.h new file mode 100644 index 00000000..adc66141 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.h @@ -0,0 +1,47 @@ +// +// $Id: MCPClassDescription+MCPEntreprise.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPClassDescription+MCPEntreprise.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 01/11/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 <Foundation/Foundation.h> +#import "MCPClassDescription.h" + +@interface MCPClassDescription (MCPEntreprise) + +#pragma mark Pseudo getters (for NSClassDescription overload) +- (NSArray *) attributeKeys; +- (NSString *) inverseRelationshipKey:(NSString *) relationshipKey; +- (NSArray *) toManyRelationshipKeys; +- (NSArray *) toOneRelationshipKeys; + +#pragma mark Specifics for MCPObject +- (NSArray *) primaryKeyAttributes; +- (NSArray *) identityAttributes; +- (MCPAttribute *) attributeWithName: (NSString *) iName; +- (MCPRelation *) relationWithName:(NSString *) iRelationName; +- (BOOL) singleIntAutoGenKey; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.m new file mode 100644 index 00000000..4c912a1f --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.m @@ -0,0 +1,186 @@ +// +// $Id: MCPClassDescription+MCPEntreprise.m 927 2009-06-24 10:53:07Z stuart02 $ +// +// MCPClassDescription+MCPEntreprise.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 01/11/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 "MCPClassDescription+MCPEntreprise.h" + +#import "MCPAttribute.h" +#import "MCPRelation.h" + +@implementation MCPClassDescription (MCPEntreprise) + +#pragma mark Pseudo getters (for NSClassDescription overload) +- (NSArray *) attributeKeys +{ + NSArray *theRet; + NSMutableArray *theKeys =[[NSMutableArray alloc] init]; + unsigned int i; + + for (i=0; i != [self countOfAttributes]; ++i) { + [theKeys insertObject:[(MCPAttribute *)[self objectInAttributesAtIndex:i] name] atIndex:i]; + } + theRet = [NSArray arrayWithArray:theKeys]; + [theKeys release]; + return theRet; +} + +- (NSString *) inverseRelationshipKey:(NSString *) relationshipKey +{ + unsigned int index = [self indexOfRelation:relationshipKey]; + + if (NSNotFound != index) { + MCPRelation *theRelation; + + theRelation = (MCPRelation *)[self objectInRelationsAtIndex:index]; + return [[theRelation inverseRelation] name]; + } + return nil; +} + +- (NSArray *) toManyRelationshipKeys +{ + NSArray *theRet; + NSMutableArray *theToManyRel = [[NSMutableArray alloc] init]; + unsigned int i, j; + + j=0; + for (i=0; i != [self countOfRelations]; ++i) { + MCPRelation *theRelation = (MCPRelation *)[self objectInRelationsAtIndex:i]; + + if ([theRelation isToMany]) { + [theToManyRel insertObject:[theRelation name] atIndex:j]; + ++j; + } + } + theRet = [NSArray arrayWithArray:theToManyRel]; + [theToManyRel release]; + return theRet; +} + +- (NSArray *) toOneRelationshipKeys; +{ + NSArray *theRet; + NSMutableArray *theToOneRel = [[NSMutableArray alloc] init]; + unsigned int i, j; + + j=0; + for (i=0; i != [self countOfRelations]; ++i) { + MCPRelation *theRelation = (MCPRelation *)[self objectInRelationsAtIndex:i]; + + if (! [theRelation isToMany]) { + [theToOneRel insertObject:[theRelation name] atIndex:j]; + ++j; + } + } + theRet = [NSArray arrayWithArray:theToOneRel]; + [theToOneRel release]; + return theRet; +} + +#pragma mark Specifics for MCPObject +- (NSArray *) primaryKeyAttributes +{ + NSMutableArray *theRet = [NSMutableArray array]; + unsigned int i, j; + + j = 0; + for (i=0; i != [self countOfAttributes]; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *)[self objectInAttributesAtIndex:i]; + + if ([theAttribute isPartOfKey]) { + [theRet insertObject:theAttribute atIndex:j]; + ++j; + } + } + return (NSArray *)theRet; +} + +- (NSArray *) identityAttributes +{ + NSMutableArray *theRet = [NSMutableArray array]; + unsigned int i, j; + + j = 0; + for (i=0; i != [self countOfAttributes]; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *)[self objectInAttributesAtIndex:i]; + + if ([theAttribute isPartOfIdentity]) { + [theRet insertObject:theAttribute atIndex:j]; + ++j; + } + } + return (NSArray *)theRet; +} + +- (MCPAttribute *) attributeWithName: (NSString *) iName +{ +// This type of implementation is NOT working : most likely the isEqual method is called on iName rather than on the objects of the array +/* + unsigned int index = [self indexOfAttribute:iName]; + + return (NSNotFound != index) ? (MCPAttribute *)[self objectInAttributesAtIndex:index] : nil ; +*/ + unsigned int i; + + for (i = 0; [attributes count] != i; ++i) { + if ([[(MCPAttribute *)[attributes objectAtIndex:i] name] isEqualToString:iName]) { + return (MCPAttribute *)[attributes objectAtIndex:i]; + } + } + return nil; +} + +- (MCPRelation *) relationWithName:(NSString *) iRelationName +{ +// This type of implementation is NOT working : most likely the isEqual method is called on iName rather than on the objects of the array +/* unsigned int index = [relations indexOfObject:iRelationName]; + + return (NSNotFound != index) ? (MCPRelation *)[relations objectAtIndex:index] : nil; +*/ + unsigned int i; + + for (i = 0; [relations count] != i; ++i) { + if ([[(MCPRelation *)[relations objectAtIndex:i] name] isEqualToString:iRelationName]) { + return (MCPRelation *)[relations objectAtIndex:i]; + } + } + return nil; +} + +- (BOOL) singleIntAutoGenKey +{ + NSArray *theKeys = [self primaryKeyAttributes]; + + if (1 == [theKeys count]) { + MCPAttribute *theSingleKey = (MCPAttribute *)[theKeys objectAtIndex:0]; + + return [theSingleKey autoGenerated] && [[theSingleKey externalType] isEqualToString:@"INT"]; + } + return NO; +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+Private.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+Private.h new file mode 100644 index 00000000..46c8a626 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+Private.h @@ -0,0 +1,45 @@ +// +// $Id: MCPClassDescription+Private.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPClassDescription+Private.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/08/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 "MCPClassDescription.h" + +@interface MCPClassDescription (Private) + +#pragma mark Setters +- (void) setAttributes:(NSArray *) iAttributes; +- (void) setRelations:(NSArray *) iRelations; +- (void) insertObject:(MCPRelation *) iRelation inIncomingsAtIndex:(unsigned int) index; +- (void) removeObjectFromIncomingsAtIndex:(unsigned int) index; + +#pragma mark Getters +- (NSArray *) incomings; +- (unsigned int) countOfIncomings; +- (MCPRelation *) objectInIncomingsAtIndex:(unsigned int) index; +- (unsigned int) indexOfIncoming:(id) iRelation; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.h new file mode 100644 index 00000000..c5119797 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.h @@ -0,0 +1,93 @@ +// +// $Id: MCPClassDescription.h 927 2009-06-24 10:53:07Z stuart02 $ +// +// MCPClassDescription.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/08/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 <Foundation/Foundation.h> + +@class MCPModel; +@class MCPAttribute; +@class MCPRelation; +@class MCPRelation; + +@interface MCPClassDescription : NSClassDescription <NSCoding> +{ +@protected + MCPModel *model; // The model where we stand + NSString *name; // Name of the class (can not use className, as it is already used by NSObject). + NSString *externalName; // Name of the table for storage + NSMutableArray *attributes; // array of the attributes of the class description + NSMutableArray *relations; // array of the relations of the class description (both origin and destination) + NSMutableArray *incomings; // array if the INCOMMING relation (just to be sure we are able to invalidate those if necessary) + Class representedClass; // the class object that the description represents. +} + +// This correspond to the method singleIntAutoGenKey in the category MCPEntreprise... which name should I change... + +#pragma mark Class methods ++ (void) initialize; + +#pragma mark Life cycle +- (id) initInModel:(MCPModel *) iModel withName:(NSString *) iName; +- (void) dealloc; + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder; +- (void) encodeWithCoder:(NSCoder *) encoder; + +#pragma mark Making new attributes and relations +- (MCPAttribute *) addNewAttributeWithName:(NSString *) iName inPosition:(int) index; +- (MCPRelation *) addNewRelationTo:(MCPClassDescription *) iTo name:(NSString *) iName inPostion:(int) index; + +#pragma mark Setters +- (void) setName:(NSString *) iName; +- (void) setExternalName:(NSString *) iExternalName; +- (void) insertObject:(MCPAttribute *) iAttribute inAttributesAtIndex:(unsigned int) index; +- (void) removeObjectFromAttributesAtIndex:(unsigned int) index; +- (void) insertObject:(MCPRelation *) iRelation inRelationsAtIndex:(unsigned int) index; +- (void) removeObjectFromRelationsAtIndex:(unsigned int) index; + +#pragma mark Getters +- (MCPModel *) model; +- (NSString *) name; +- (NSString *) externalName; +- (NSArray *) attributes; +- (unsigned int) countOfAttributes; +- (MCPAttribute *) objectInAttributesAtIndex:(unsigned int) index; +- (unsigned int) indexOfAttribute:(id) iAttribute; +- (NSArray *) relations; +- (unsigned int) countOfRelations; +- (MCPRelation *) objectInRelationsAtIndex:(unsigned int) index; +- (unsigned int) indexOfRelation:(id) iRelation; +- (Class) representedClass; + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject; + +#pragma mark Output for logging +- (NSString *) descriptionWithLocale:(NSDictionary *) locale; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.m new file mode 100644 index 00000000..fcd79e01 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.m @@ -0,0 +1,372 @@ +// +// $Id: MCPClassDescription.m 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPClassDescription.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/08/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 "MCPClassDescription.h" +#import "MCPClassDescription+Private.h" + +#import "MCPEntrepriseNotifications.h" + +#import "MCPModel.h" +#import "MCPAttribute.h" +#import "MCPRelation.h" + +@implementation MCPClassDescription + +#pragma mark Class methods ++ (void) initialize +{ + if (self = [MCPClassDescription class]) { + [self setVersion:010101]; // Major.Minor.Revision -> MaMiRe + } + return; +} + +#pragma mark Life cycle +- (id) initInModel:(MCPModel *) iModel withName:(NSString *) iName +{ + self = [super init]; + if (self) { + model = iModel; + [self setName:iName]; + attributes = [[NSMutableArray alloc] init]; + relations = [[NSMutableArray alloc] init]; + incomings = [[NSMutableArray alloc] init]; + representedClass = nil; + // NSLog(@"MAKING a new object : %@", self); + } + return self; +} + +- (void) dealloc +{ +// NSArray *theRelations; +// unsigned int i; + + [name release]; + [externalName release]; + [attributes release]; + while ([relations count]) { + [(MCPRelation *)[relations objectAtIndex:0] invalidateRelation]; + } + [relations release]; + while ([incomings count]) { + [(MCPRelation *)[incomings objectAtIndex:0] invalidateRelation]; + } + [incomings release]; + [super dealloc]; +} + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder +{ + self = [super init]; + if ((self) && ([decoder allowsKeyedCoding])) { + model = [decoder decodeObjectForKey:@"MCPmodel"]; +// NSLog(@"in MCPClassDescription initWithCoder, model = %@ (pointer = %p)", model, model); + [self setName:[decoder decodeObjectForKey:@"MCPname"]]; + [self setExternalName:[decoder decodeObjectForKey:@"MCPexternalName"]]; + [self setAttributes:[decoder decodeObjectForKey:@"MCPattributes"]]; +// [self setRelations:[decoder decodeObjectForKey:@"MCPrelations"]]; + relations = [[NSMutableArray alloc] init]; + incomings = [[NSMutableArray alloc] init]; + representedClass = nil; + [decoder decodeObjectForKey:@"MCPrelations"]; // The relation get linked properly while initted. + } + else { + NSLog(@"For some reason, unable to decode MCPClassDescription from the coder!!!"); + } + + return self; +} + +- (void) encodeWithCoder:(NSCoder *) encoder +{ + if (! [encoder allowsKeyedCoding]) { + NSLog(@"In MCPClassDescription -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!"); + return; + } +// [encoder encodeObject:[self model] forKey:@"MCPmodel"]; + [encoder encodeConditionalObject:[self model] forKey:@"MCPmodel"]; + [encoder encodeObject:[self name] forKey:@"MCPname"]; + [encoder encodeObject:[self externalName] forKey:@"MCPexternalName"]; + [encoder encodeObject:[self attributes] forKey:@"MCPattributes"]; + [encoder encodeObject:[self relations] forKey:@"MCPrelations"]; + [encoder encodeObject:@"1.1.1" forKey:@"MCPversion"]; + return; +} + +#pragma mark Making new attributes and relations +- (MCPAttribute *) addNewAttributeWithName:(NSString *) iName inPosition:(int) index +{ + MCPAttribute *theAttribute = [[MCPAttribute alloc] initForClassDescription:self withName:iName]; + +// [self addAttribute:theAttribute]; + [self insertObject:theAttribute inAttributesAtIndex:(index < 0) ? ([self countOfAttributes] + index + 1) : index]; + [theAttribute release]; + return theAttribute; +} + +- (MCPRelation *) addNewRelationTo:(MCPClassDescription *) iTo name:(NSString *) iName inPostion:(int) index +{ + MCPRelation *theRelation = [[MCPRelation alloc] initWithName:iName from:self to:iTo]; + + [theRelation release]; + return theRelation; +} + +#pragma mark Setters +- (void) setName:(NSString *) iName +{ + if (iName != name) { + [name release]; + name = [iName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; + representedClass = nil; + } +} + +- (void) setExternalName:(NSString *) iExternalName +{ + if (iExternalName != externalName) { + [externalName release]; + externalName = [iExternalName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; + } +} + +- (void) insertObject:(MCPAttribute *) iAttribute inAttributesAtIndex:(unsigned int) index +{ + [attributes insertObject:iAttribute atIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; +} + +- (void) removeObjectFromAttributesAtIndex:(unsigned int) index +{ + [attributes removeObjectAtIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; +} + +- (void) insertObject:(MCPRelation *) iRelation inRelationsAtIndex:(unsigned int) index +{ + [relations insertObject:iRelation atIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; +} + +- (void) removeObjectFromRelationsAtIndex:(unsigned int) index +{ + [relations removeObjectAtIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; +} + +#pragma mark Getters +- (MCPModel *) model +{ + return model; +} + +- (NSString *) name +{ + return name; +} + +- (NSString *) externalName +{ + return externalName; +} + +- (NSArray *) attributes +{ + return [NSArray arrayWithArray:attributes]; +} + +- (unsigned int) countOfAttributes +{ + return [attributes count]; +} + +- (MCPAttribute *) objectInAttributesAtIndex:(unsigned int) index +{ + return (MCPAttribute *)((NSNotFound != index) ? [attributes objectAtIndex:index] : nil); +} + +- (unsigned int) indexOfAttribute:(id) iAttribute +{ + return [attributes indexOfObject:iAttribute]; +} + +- (NSArray *) relations +{ + return [NSArray arrayWithArray:relations]; +} + +- (unsigned int) countOfRelations +{ + return [relations count]; +} + +- (MCPRelation *) objectInRelationsAtIndex:(unsigned int) index +{ + return (MCPRelation *)((NSNotFound != index) ? [relations objectAtIndex:index] : nil); +} + +- (unsigned int) indexOfRelation:(id) iRelation +{ + return [relations indexOfObject:iRelation]; +} + +- (Class) representedClass +{ + if (representedClass) { + return representedClass; + } + representedClass = NSClassFromString(name); + return representedClass; +} + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject +// Equal to another class description if they have the same name. +// Equal to a string if the string is equal to the className of the class description. +{ + if ([iObject isKindOfClass:[MCPClassDescription class]]) { + return [name isEqualToString:[(MCPClassDescription *)iObject name]]; + } + if ([iObject isKindOfClass:[NSString class]]) { + return [name isEqualToString:(NSString *)iObject]; + } + return NO; +} + +/* +- (NSString *) description +{ + return [NSString stringWithFormat:@"<MCPClassDescription for class named %@ : %p>", [self name], self]; +} + +- (NSString *) descriptionWithLocale:(NSDictionary *) locale +{ + return [self description]; +} +*/ + +#pragma mark Output for logging +- (NSString *) descriptionWithLocale:(NSDictionary *) locale +{ + NSMutableString *theOutput = [NSMutableString string]; + unsigned i; + + [theOutput appendFormat:@"MCPClassDescription for class : %@ (table : %@)\n", [self name], [self externalName]]; + for (i=0; [attributes count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [attributes objectAtIndex:i]; + + [theOutput appendFormat:@"attribute %u, name = %@, column = %@. Allows null : %c\n", i, [theAttribute name], [theAttribute externalName], ([theAttribute allowsNull] ? 'Y' : 'N')]; + } + return theOutput; +} + +#pragma mark For debugging the retain counting +- (id) retain +{ + [super retain]; + return self; +} + +- (void) release +{ + [super release]; + return; +} + +@end + +@implementation MCPClassDescription (Private) + +#pragma mark Setters +- (void) setAttributes:(NSArray *) iAttributes +{ + if (iAttributes != attributes) { + [attributes release]; + attributes = [[NSMutableArray alloc] initWithArray:iAttributes]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; + } +} + +- (void) setRelations:(NSArray *) iRelations +{ + if (iRelations != relations) { + [relations release]; + relations = [[NSMutableArray alloc] initWithArray:iRelations]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; + } +} + +- (void) insertObject:(MCPRelation *) iRelation inIncomingsAtIndex:(unsigned int) index +{ + if ([iRelation destination] == self) { + [incomings insertObject:iRelation atIndex:index]; + } + else { + NSLog(@"in -[MCPClassDescription+Private insertObject:inIncomingsAtIndex:]. ERRROR : self is NOT the destination of the relation"); + } +} + +- (void) removeObjectFromIncomingsAtIndex:(unsigned int) index +{ + [incomings removeObjectAtIndex:index]; +} + +#pragma mark Getters +- (NSArray *) incomings +{ + return [NSArray arrayWithArray:incomings]; +} + +- (unsigned int) countOfIncomings +{ + return [incomings count]; +} + +- (MCPRelation *) objectInIncomingsAtIndex:(unsigned int) index +{ + return (MCPRelation *)[incomings objectAtIndex:index]; +} + +- (unsigned int) indexOfIncoming:(id) iRelation +{ + return [incomings indexOfObject:iRelation]; +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.h new file mode 100644 index 00000000..496a448c --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.h @@ -0,0 +1,36 @@ +// +// $Id: MCPEntrepriseNotifications.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPEnterpriseNotifications.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/08/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 <Foundation/Foundation.h> + +#pragma mark Name for notification + +extern NSString *MCPModelChangedNotification; +extern NSString *MCPClassDescriptionChangedNotification; +extern NSString *MCPAttributeChangedNotification; +extern NSString *MCPRelationChangedNotification; diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.m new file mode 100644 index 00000000..63e580ad --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.m @@ -0,0 +1,34 @@ +// +// $Id: MCPEntrepriseNotifications.m 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPEnterpriseNotifications.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/08/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 <Foundation/Foundation.h> + +NSString *MCPModelChangedNotification = @"Model has changed"; +NSString *MCPClassDescriptionChangedNotification = @"ClassDescription has changed"; +NSString *MCPAttributeChangedNotification = @"Attribute has changed"; +NSString *MCPRelationChangedNotification = @"Relation has changed"; diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.h new file mode 100644 index 00000000..c3882fee --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.h @@ -0,0 +1,74 @@ +// +// $Id: MCPJoin.h 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPJoin.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 18/08/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 <Foundation/Foundation.h> + +@class MCPModel; +@class MCPClassDescription; +@class MCPAttribute; +@class MCPRelation; +@class MCPRelation; + +@interface MCPJoin : NSObject <NSCoding> +{ +@protected + // Note that NONE of these attributes are retained!!! + // Instead all these objects are notified of the existence of the join + // and are responsible to invalidate/delete it if necessary. + MCPRelation *relation; + MCPAttribute *origin; + MCPAttribute *destination; +} + +#pragma mark Class methods ++ (void) initialize; + +#pragma mark Life cycle +- (id) initForRelation:(MCPRelation *) iRelation from:(MCPAttribute *) iOrigin to:(MCPAttribute *) iDestination; +- (void) invalidate; +- (void) dealloc; + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder; +- (void) encodeWithCoder:(NSCoder *) encoder; + +#pragma mark Setters +// No setter for relation : should be set at init time! +- (void) setOrigin:(MCPAttribute *) iOrigin; +- (void) setDestination:(MCPAttribute *) iDestination; + +#pragma mark Getters +- (MCPRelation *) relation; +- (MCPAttribute *) origin; +- (MCPAttribute *) destination; +- (unsigned int) index; + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.m new file mode 100644 index 00000000..cf202196 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.m @@ -0,0 +1,183 @@ +// +// $Id: MCPJoin.m 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPJoin.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 18/08/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 "MCPJoin.h" + +#import "MCPModel.h" +#import "MCPClassDescription.h" +#import "MCPAttribute.h" +#import "MCPRelation.h" +#import "MCPRelation.h" + +@implementation MCPJoin + +#pragma mark Class methods ++ (void) initialize +{ + if (self = [MCPJoin class]) { + [self setVersion:010101]; // Ma.Mi.Re -> MaMiRe + } + return; +} + +#pragma mark Life cycle +- (id) initForRelation:(MCPRelation *) iRelation from:(MCPAttribute *) iOrigin to:(MCPAttribute *) iDestination; +{ + self = [super init]; + if (self) { + relation = iRelation; + [self setOrigin:iOrigin]; + [self setDestination:iDestination]; + } + return self; +} + +- (void) invalidate +{ + [self retain]; + NSLog(@"Enterring -[MCPJoin invalidate], retain count is %u (after retaining : should be 4)", [self retainCount]); + [origin removeObjectFromJoinsAtIndex:[origin indexOfJoinIdenticalTo:self]]; + [destination removeObjectFromJoinsAtIndex:[destination indexOfJoinIdenticalTo:self]]; + [relation removeObjectFromJoinsAtIndex:[relation indexOfJoinIdenticalTo:self]]; + NSLog(@"Enterring -[MCPJoin invalidate], retain count is %u (before releasing : should be 1)", [self retainCount]); + [self release]; + return; +} + +- (void) dealloc +{ + // Nothing to release, because the attributes are NOT retained. + [super dealloc]; +} + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder +{ + self = [super init]; + if ((self) && ([decoder allowsKeyedCoding])) { + relation = [decoder decodeObjectForKey:@"MCPrelation"]; + [self setOrigin:[decoder decodeObjectForKey:@"MCPorigin"]]; + [self setDestination:[decoder decodeObjectForKey:@"MCPdestination"]]; + } + else { + NSLog(@"For some reason, unable to decode MCPJoin from the coder!!!"); + } + return self; +} + +- (void) encodeWithCoder:(NSCoder *) encoder +{ + if (! [encoder allowsKeyedCoding]) { + NSLog(@"In MCPJoin -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!"); + return; + } + [encoder encodeObject:[self relation] forKey:@"MCPrelation"]; + [encoder encodeObject:[self origin] forKey:@"MCPorigin"]; + [encoder encodeObject:[self destination] forKey:@"MCPdestination"]; +} + +#pragma mark Setters +- (void) setOrigin:(MCPAttribute *) iOrigin +{ + if (origin != iOrigin) { + if (origin) { + [origin removeObjectFromJoinsAtIndex:[origin indexOfJoinIdenticalTo:self]]; + } + origin = iOrigin; + if (origin) { + [origin insertObject:self inJoinsAtIndex:[origin countOfJoins]]; + } + } +} + +- (void) setDestination:(MCPAttribute *) iDestination +{ + if (destination != iDestination) { + if (destination) { + [destination removeObjectFromJoinsAtIndex:[destination indexOfJoinIdenticalTo:self]]; + } + destination = iDestination; + if (destination) { + [destination insertObject:self inJoinsAtIndex:[destination countOfJoins]]; + } + } +} + +#pragma mark Getters +- (MCPRelation *) relation +{ + return relation; +} + +- (MCPAttribute *) origin +{ + return origin; +} + +- (MCPAttribute *) destination +{ + return destination; +} + +- (unsigned int) index +{ + return [relation indexOfJoinIdenticalTo:self]; +} + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject +{ + if ([iObject isKindOfClass:[MCPJoin class]]) { + MCPJoin *theJoin = (MCPJoin *)iObject; + + return ([relation isEqual:[theJoin relation]]) && ([origin isEqual:[theJoin origin]]) && ([destination isEqual:[theJoin destination]]); + } + if ([iObject isKindOfClass:[NSDictionary class]]) { + NSDictionary *theDict = (NSDictionary *)iObject; + + return ([relation isEqual:[theDict valueForKey:@"relation"]]) && ([origin isEqual:[theDict valueForKey:@"origin"]]) && ([destination isEqual:[theDict valueForKey:@"destination"]]); + } + return NO; +} + +#pragma mark For debugging the retain counting +- (id) retain +{ + [super retain]; + + return self; +} + +- (void) release +{ + [super release]; + + return; +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.h new file mode 100644 index 00000000..cc307752 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.h @@ -0,0 +1,35 @@ +// +// $Id: MCPModel+MCPEntreprise.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPModel+MCPEntreprise.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 01/11/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://code.google.com/p/sequel-pro/> + +#import "MCPModel.h" + +@interface MCPModel (MCPEntreprise) + +- (void) registerAsClassDescriptionServer; +- (void) registerDescriptionForClass:(NSNotification *) notification; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.m new file mode 100644 index 00000000..cb9ebbaa --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.m @@ -0,0 +1,57 @@ +// +// $Id: MCPModel+MCPEntreprise.m 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPModel+MCPEntreprise.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 01/11/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 "MCPModel+MCPEntreprise.h" + +#import "MCPObject.h" + +@implementation MCPModel (MCPEntreprise) + +#pragma mark Work as a class description server + +- (void) registerAsClassDescriptionServer +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registerDescriptionForClass:) name:NSClassDescriptionNeededForClassNotification object:nil]; +} + +- (void) registerDescriptionForClass:(NSNotification *) notification +{ + Class theClass = [notification object]; + + if ([theClass isSubclassOfClass:[MCPObject class]]) { + + NSString *theClassName = NSStringFromClass(theClass); + unsigned int index = [self indexOfClassDescription:theClassName]; + + if (NSNotFound != index) { + [NSClassDescription registerClassDescription:(NSClassDescription *)[self objectInClassDescriptionsAtIndex:index] forClass:theClass]; + } + } +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.h new file mode 100644 index 00000000..200b4a4b --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.h @@ -0,0 +1,84 @@ +// +// $Id: MCPModel.h 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPModel.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/08/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 <Foundation/Foundation.h> + +@class MCPClassDescription; +@class MCPAttribute; +@class MCPRelation; + +@interface MCPModel : NSObject <NSCoding> +{ +@protected + NSString *name; // Name of the model ... useless. + NSMutableArray *classDescriptions; // Order of the class descriptions in the model. + BOOL usesInnoDBTables; // The database should use InnoDB tables. + // Might add a string holding définition of tables. + // Might also add some sort of template for generated files (at least the header). +} + +#pragma mark Class methods ++ (void) initialize; + +#pragma mark Life cycle +- (id) initWithName:(NSString *) iName; +- (void) dealloc; + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder; +- (void) encodeWithCoder:(NSCoder *) encoder; + +#pragma mark Making new class description +- (MCPClassDescription *) addNewClassDescriptionWithName:(NSString *) iName inPosition:(int) index; + +#pragma mark Setters +- (void) setName:(NSString *) iName; +- (void) setClassDescriptions:(NSArray *) iClassDescriptions; +- (void) insertObject:(MCPClassDescription *) iClassDescription inClassDescriptionsAtIndex:(unsigned int) index; +- (void) removeObjectFromClassDescriptionsAtIndex:(unsigned int) index; +- (void) setUsesInnoDBTables:(BOOL) iUsesInnoDB; + +// Deprecated : non KVC +//- (void) removeClassDescription:(MCPClassDescription *) iClassDescription; +//- (void) addClassDescription:(MCPClassDescription *) iClassDescription; + +#pragma mark Getters +- (NSString *) name; +- (NSArray *) classDescriptions; +- (unsigned int) countOfClassDescriptions; +- (MCPClassDescription *) objectInClassDescriptionsAtIndex:(unsigned int) index; +- (unsigned int) indexOfClassDescription:(id) iClassDescription; +- (BOOL) usesInnoDBTables; + +// Deprecated : non KVC +//- (MCPClassDescription *) classDescriptionWithClassName:(NSString *) iClassDescriptionClassName; + +#pragma mark Output for logging +- (NSString *) descriptionWithLocale:(NSDictionary *) locale; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.m new file mode 100644 index 00000000..8f9393f2 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.m @@ -0,0 +1,230 @@ +// +// $Id: MCPModel.m 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPModel.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/08/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 "MCPModel.h" + +#import "MCPEntrepriseNotifications.h" + +#import "MCPClassDescription.h" +#import "MCPAttribute.h" +#import "MCPRelation.h" + +@implementation MCPModel + +#pragma mark Class methods ++ (void) initialize +{ + if (self = [MCPModel class]) { + [self setVersion:010101]; // Ma.Mi.Re -> MaMiRe + } + return; +} + +#pragma mark Life cycle +- (id) initWithName:(NSString *) iName +{ + self = [super init]; + if (self) { + [self setName:iName]; + classDescriptions = [[NSMutableArray alloc] init]; + } + + return self; +} + +- (void) dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [name release]; + [classDescriptions release]; + [super dealloc]; +} + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder +{ + self = [super init]; + if ((self) && ([decoder allowsKeyedCoding])) { + [self setName:[decoder decodeObjectForKey:@"MCPname"]]; + [self setClassDescriptions:[decoder decodeObjectForKey:@"MCPclassDescriptions"]]; + [self setUsesInnoDBTables:[decoder decodeBoolForKey:@"MCPusesInnoDBTables"]]; + } + else { + NSLog(@"For some reason, unable to decode MCPModel from the coder!!!"); + } + + return self; +} + +- (void) encodeWithCoder:(NSCoder *) encoder +{ + if (! [encoder allowsKeyedCoding]) { + NSLog(@"In MCPModel -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!"); + return; + } +// [super encodeWithCoder:encoder]; + [encoder encodeObject:[self name] forKey:@"MCPname"]; + [encoder encodeObject:[self classDescriptions] forKey:@"MCPclassDescriptions"]; + [encoder encodeBool:[self usesInnoDBTables] forKey:@"MCPusesInnoDBTables"]; + [encoder encodeObject:@"1.1.1" forKey:@"MCPversion"]; + return; +} + +#pragma mark Making new class description +- (MCPClassDescription *) addNewClassDescriptionWithName:(NSString *) iName inPosition:(int) index; +{ + MCPClassDescription *theClassDescription = [[MCPClassDescription alloc] initInModel:self withName:iName]; + +// [self addClassDescription:theClassDescription]; + [self insertObject:theClassDescription inClassDescriptionsAtIndex:(index < 0) ? ([self countOfClassDescriptions] + index + 1) : index]; + [theClassDescription release]; + return theClassDescription; +} + +#pragma mark Setters +- (void) setName:(NSString *) iName +{ + if (iName != name) { + [name release]; + name = [iName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; + } +} + +- (void) setClassDescriptions:(NSArray *) iClassDescriptions +{ + if (iClassDescriptions != classDescriptions) { + [classDescriptions release]; + classDescriptions = [[NSMutableArray alloc] initWithArray:iClassDescriptions]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; + } +} + +- (void) insertObject:(MCPClassDescription *) iClassDescription inClassDescriptionsAtIndex:(unsigned int) index +{ + [classDescriptions insertObject:iClassDescription atIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; +} + +- (void) removeObjectFromClassDescriptionsAtIndex:(unsigned int) index +{ + [classDescriptions removeObjectAtIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; +} + +- (void) setUsesInnoDBTables:(BOOL) iUsesInnoDB +{ + usesInnoDBTables = iUsesInnoDB; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; +} + +// Deprecated : non KVC +/* +- (void) removeClassDescription:(MCPClassDescription *) iClassDescription +{ + [classDescriptions removeObject:iClassDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; +} + + - (void) addClassDescription:(MCPClassDescription *) iClassDescription + { + [classDescriptions addObject:iClassDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; + } + + */ + +#pragma mark Getters +- (NSString *) name +{ + return name; +// return [NSString stringWithString:name]; +} + +- (NSArray *) classDescriptions +{ + return [NSArray arrayWithArray:classDescriptions]; +} + +- (unsigned int) countOfClassDescriptions +{ + return [classDescriptions count]; +} + +- (MCPClassDescription *) objectInClassDescriptionsAtIndex:(unsigned int) index +{ + return (MCPClassDescription *)((NSNotFound != index) ? [classDescriptions objectAtIndex:index] : nil); +} + +- (MCPClassDescription *) classDescriptionWithClassName:(NSString *) iClassDescriptionClassName +{ +// Given the implementation of isEqual: for the MCPClassDescription, one should be able to use NSArray method directly: + /* unsigned int i; + + for (i=0; ([classDescriptions count] != i) && (! [iClassDescriptionClassName isEqualToString:[(MCPClassDescription *) [classDescriptions objectAtIndex:i] className]]); ++i ) { + } + return (i == [classDescriptions count]) ? nil : (MCPClassDescription *)[classDescriptions objectAtIndex:i]; + */ + unsigned int theIndex = [classDescriptions indexOfObject:iClassDescriptionClassName]; + return (NSNotFound == theIndex) ? nil : [classDescriptions objectAtIndex:theIndex]; +} + +- (unsigned int) indexOfClassDescription:(id) iClassDescription +{ + return [classDescriptions indexOfObject:iClassDescription]; +} + +- (BOOL) usesInnoDBTables +{ + return usesInnoDBTables; +} + +// Deprecated : non KVC + +#pragma mark Output for logging +- (NSString *) descriptionWithLocale:(NSDictionary *) locale +{ + return [NSString stringWithFormat:@"<MCPModel with name %@ : %p>", [self name], self]; +} + +#pragma mark For debugging the retain counting +- (id) retain +{ + [super retain]; + + return self; +} + +- (void) release +{ + [super release]; + + return; +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.h new file mode 100644 index 00000000..7e6aa3b3 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.h @@ -0,0 +1,126 @@ +// +// $Id: MCPObject.h 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPObject.h +// 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 <Foundation/Foundation.h> + +/*" Possible return code on some operations of the database interaction. "*/ +typedef enum { + MCPDBReturnUnknown = 0, /*"Unknown state, should not happen."*/ + MCPDBReturnDeleted = 1, /*"The entry have been successfuly deleted from DB."*/ + MCPDBReturnUsed = 2, /*"The entry can not be removed, because some entries are still connected to it (some delete restrict/inhibit delete)."*/ + MCPDBReturnNone = 3, /*"No entry exist with this Id."*/ + MCPDBReturnNew = 4, /*"The entry was indeed new and inserted in the database."*/ + MCPDBReturnUpdated = 5, /*"The entry was updated in the DB."*/ + MCPDBReturnIncompleteKey = 6, /*"Part of the primary key is missing, action not taken."*/ + MCPDBReturnMultiple = 7, /*"Multiple rows are found with a query supposed to return at most one row."*/ + MCPDBReturnNoIdentity = 8, /*"The object does not have attributes that defines identity."*/ + MCPDBReturnNoKey = 9, /*"There is no primary key defined for this entity."*/ + MCPDBReturnNoConnection = 10, /*"The MCPObject is not having a connection."*/ + MCPDBReturnWrongRelationOrigin = 11, /*"Looking for a relation not which origin is not of the specified class."*/ + MCPDBReturnWrongRelationCardinality = 12, /*"Using a method assuming a cardinality of the relation while the relation as the other one."*/ + MCPDBReturnNoSuchRelation = 13, /*"There is no relation with such a name starting from this class."*/ + MCPDBReturnNotTarget = 14, /*"Tried to remove an object from a relation, while the objects does NOT belong to the relation."*/ + MCPDBReturnOK = 100 /*"Everything went OK."*/ +} MCPDBReturnCode; + +@class MCPConnection; +@class MCPClassDescription; +@class MCPRelation; + +@interface MCPObject : NSObject { + MCPClassDescription *classDescription; + MCPConnection *connection; +} + +#pragma mark Life of the Object +/*" Life of the object "*/ +- (id) init; +- (id) initWithDictionary:(NSDictionary *) dictionary; + +- (void) dealloc; + +- (void) setAttributesToDefault; + +#pragma mark Accessors +/*" Accessor(s) "*/ +- (MCPClassDescription *) classDescription; +- (MCPConnection *) connection; + +- (void) setConnection:(MCPConnection *) iConnection; + +#pragma mark Database interface +/*" Database interface "*/ +- (id) readFromDBRow:(NSDictionary *) iDictionary withTableName:(NSString *) iTableName; +- (MCPDBReturnCode) setPrimaryKey:(id) iDictionary andFetchFromDB:(MCPConnection *) iConnection; +//- (MCPDBReturnCode) setPrimaryKey:(NSDictionary *) iDictionary andFetchFromDB:(MCPConnection *) iConnection; +- (NSDictionary *) checkDBId; // the returned dictionary contains a MCPDBReturnCode key with the return code. +- (NSDictionary *) saveInDB; // the returned dictionary contains a MCPDBReturnCode key with the return code. +- (MCPDBReturnCode) getAutoGenerated; +- (MCPDBReturnCode) updateInDB; +- (MCPDBReturnCode) deleteInDB; ++ (MCPDBReturnCode) deleteInDBUsingConnection:(MCPConnection *) iConnection withId:(id) iId; + +#pragma mark Handling relations +/*" Handling realtions "*/ +- (id) getTargetOfRelation:(MCPRelation *) iRelation; +- (id) getTargetOfRelationNamed:(NSString *) iRelationName; +- (MCPDBReturnCode) setTarget:(id) iTarget forRelation:(MCPRelation *) iRelation; +- (MCPDBReturnCode) setTarget:(id) iTarget forRelationNamed:(NSString *) iRelationName; +- (unsigned int) countTargetForRelation:(MCPRelation *) iRelation; +- (unsigned int) countTargetForRelationNamed:(NSString *) iRelationName; +- (MCPObject *) getTargetOfRelation:(MCPRelation *) iRelation atIndex:(unsigned int) iIndex; +- (MCPObject *) getTargetOfRelationNamed:(NSString *) iRelationName atIndex:(unsigned int) iIndex; +- (MCPDBReturnCode) addTarget:(MCPObject *) iTarget toRelation:(MCPRelation *) iRelation; +- (MCPDBReturnCode) addTarget:(MCPObject *) iTarget toRelationNamed:(NSString *) iRelationName; +- (MCPDBReturnCode) removeTarget:(MCPObject *) iTarget toRelation:(MCPRelation *) iRelation; +- (MCPDBReturnCode) removeTarget:(MCPObject *) iTarget toRelationNamed:(NSString *) iRelationName; +- (MCPDBReturnCode) removeTargetToRelation:(MCPRelation *) iRelation atIndex:(unsigned int) iIndex; +- (MCPDBReturnCode) removeTargetToRelationNamed:(NSString *) iRelationName atIndex:(unsigned int) iIndex; +- (unsigned int) indexOfTarget:(MCPObject *) iTarget inRelation:(MCPRelation *) iRelation; +- (unsigned int) indexOfTarget:(MCPObject *) iTarget inRelationNamed:(NSString *) iRelationName; + +#pragma mark Utilities +/*" Utility methods "*/ +- (id) defaultValueForKey:(NSString *) iKey; +- (NSDictionary *) primaryKey; + +/*" Testing equality (VERY important for relation management)"*/ +- (BOOL) isEqual:(id) iObject; + +#pragma mark Output +/*" Output : "*/ +- (NSString *) description; +- (NSString *) descriptionWithLocale:(NSDictionary *) locale; + +#pragma mark Ordering the array for relations +- (NSString *) orderSQLForClassDescription:(MCPClassDescription *) iClassDescription; + +/*" Anti-crash method... "*/ +- (void) setNilValueForKey:(NSString *) iKey; + +@end 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 diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation+Private.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation+Private.h new file mode 100644 index 00000000..c8683aab --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation+Private.h @@ -0,0 +1,46 @@ +// +// $Id: MCPRelation+Private.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPRelation+Private.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 11/08/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 "MCPRelation.h" + +@interface MCPRelation (Private) + +#pragma mark Making some work +- (void) invalidateJoins; // Check that the joins are realistics. + +#pragma mark Setters +- (void) setOrigin:(MCPClassDescription *) iOrigin; +//- (void) setJoins:(NSArray *) iJoins; + +#pragma mark Getters +- (MCPModel *) model; + +#pragma mark Fro the controller layer and the UI +- (void) addNewDefaultJoin; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.h new file mode 100644 index 00000000..43ff027a --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.h @@ -0,0 +1,112 @@ +// +// $Id: MCPRelation.h 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPRelation.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 11/08/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 <Foundation/Foundation.h> + +@class MCPModel; +@class MCPClassDescription; +@class MCPAttribute; +@class MCPJoin; + +typedef enum { + OnDeleteNullify = 1, + OnDeleteDeny = 2, + OnDeleteCascade = 3, + OnDeleteDefault = 4, + OnDeleteNoAction = 5 +} MCPRelationDeleteRule; + +@interface MCPRelation : NSObject <NSCoding> +{ +@protected + NSString *name; // Name of the relation + MCPRelationDeleteRule deleteRule; // Delete rule : what to do of the destination when origin is deleted + MCPRelation *inverseRelation; // The inverse relation (or nil if no inverse present) + MCPClassDescription *origin; // The class description from which the relation originate + MCPClassDescription *destination; // The class description to which the relation arrives + NSMutableArray *joins; // Joining attributes (array of MCPJoin) + BOOL isToMany; // Is the relation to many (or to one) + BOOL isMandatory; // Is the relation mandatory for the class description (origin) + BOOL ownsDestination; // The origin class description owns the destination class description(ies) +} + +#pragma mark Class methods ++ (void) initialize; + ++ (NSArray *) existingDeleteRules; +- (NSArray *) existingDeleteRules; + +#pragma mark Life cycle +- (id) initWithName:(NSString *) iName from:(MCPClassDescription *) iFrom to:(MCPClassDescription *) iTo; +- (void) invalidateRelation; +- (void) dealloc; + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder; +- (void) encodeWithCoder:(NSCoder *) encoder; + +#pragma mark Managing joins +//- (MCPJoin *) addNewJoin; +- (MCPJoin *) addJoinFrom:(MCPAttribute *) iFrom to:(MCPAttribute *) iTo; +- (void) removeJoinFrom:(MCPAttribute *) iFrom to:(MCPAttribute *) iTo; +//- (void) unjoinAttribute:(MCPAttribute *) iAttribute; + +#pragma mark Setters +- (void) setDestination:(MCPClassDescription *) iDestination; +- (void) setName:(NSString *) iName; +- (void) setDeleteRule:(MCPRelationDeleteRule) iDeleteRule; +- (void) setInverseRelation:(MCPRelation *) iInverseRelation; +- (void) insertObject:(MCPJoin *) iJoin inJoinsAtIndex:(unsigned int) index; +- (void) removeObjectFromJoinsAtIndex:(unsigned int) index; +- (void) setIsToMany:(BOOL) iIsToMany; +- (void) setIsMandatory:(BOOL) iIsMandatory; +- (void) setOwnsDestintation:(BOOL) iOwnsDestination; + +#pragma mark Getters +- (NSString *) name; +- (MCPRelationDeleteRule) deleteRule; +- (MCPRelation *) inverseRelation; +- (MCPClassDescription *) origin; +- (MCPClassDescription *) destination; +- (NSArray *) joins; +- (unsigned int) countOfJoins; +- (MCPJoin *) objectInJoinsAtIndex:(unsigned int) index; +- (unsigned int) indexOfJoinIdenticalTo:(id) iJoin; +- (BOOL) isToMany; +- (BOOL) isMandatory; +- (BOOL) ownsDestination; + +#pragma mark Some Usefull methods +- (MCPAttribute *) destinationAttributeForOrigin:(MCPAttribute *) iFrom; +- (MCPAttribute *) originAttributeForDestination:(MCPAttribute *) iTo; + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject; +- (NSString *) descriptionWithLocale:(NSDictionary *) locale; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.m new file mode 100644 index 00000000..57186fdc --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.m @@ -0,0 +1,486 @@ +// +// $Id: MCPRelation.m 927 2009-06-24 10:53:07Z stuart02 $ +// +// MCPRelation.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 11/08/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 "MCPRelation.h" +#import "MCPRelation+Private.h" + +#import "MCPEntrepriseNotifications.h" + +#import "MCPModel.h" +#import "MCPClassDescription.h" +#import "MCPClassDescription+Private.h" +#import "MCPAttribute.h" + +#import "MCPJoin.h" + +static NSArray *MCPexistingDeleteRules; + +@implementation MCPRelation + +#pragma mark Class methods ++ (void) initialize +{ + if (self = [MCPRelation class]) { + NSMutableArray *theExistingDeleteRules = [[NSMutableArray alloc] init]; + + [self setVersion:010101]; // Ma.Mi.Re -> MaMiRe + + [theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:OnDeleteNullify], @"tag", @"Nullify", @"name", nil]]; + [theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:OnDeleteDeny], @"tag", @"Deny", @"name", nil]]; + [theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:OnDeleteCascade], @"tag", @"Cascade", @"name", nil]]; + [theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:OnDeleteDefault], @"tag", @"Default", @"name", nil]]; + [theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:OnDeleteNoAction], @"tag", @"No Action", @"name", nil]]; + MCPexistingDeleteRules = [[NSArray alloc] initWithArray:theExistingDeleteRules]; + [theExistingDeleteRules release]; + } + return; +} + ++ (NSArray *) existingDeleteRules +{ + return MCPexistingDeleteRules; +} + +- (NSArray *) existingDeleteRules +{ + return [MCPRelation existingDeleteRules]; +} + + +#pragma mark Life cycle +- (id) initWithName:(NSString *) iName from:(MCPClassDescription *) iFrom to:(MCPClassDescription *) iTo +{ + self = [super init]; + if (self) { + [self setName:iName]; + [self setOrigin:iFrom]; + [self setDestination:iTo]; + joins = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) invalidateRelation +{ + [self retain]; // To be sure not to be released before the end of the method + [self invalidateJoins]; // Remove each of the joins (so that attributes get notified) +/* + [origin removeObjectFromRelationsAtIndex:[[origin relations] indexOfObjectIdenticalTo:self]]; + origin = nil; + [destination removeObjectFromIncomingsAtIndex:[[destination incomings] indexOfObjectIdenticalTo:self]]; + destination = nil; +*/ + [self setOrigin:nil]; + [self setDestination:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + [self release]; +} + +- (void) dealloc +{ + [joins release]; // Should be empty by now... + [name release]; +// The inverse relation don't have an inverse relation any more... +// [inverseRelation setInverseRelation:nil]; + [self setInverseRelation:nil]; +// Other are weak references. + [super dealloc]; +} + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder +{ + self = [super init]; + if ((self) && ([decoder allowsKeyedCoding])) { + [self setName:[decoder decodeObjectForKey:@"MCPname"]]; + [self setDeleteRule:(MCPRelationDeleteRule)[decoder decodeInt32ForKey:@"MCPdeleteRule"]]; + if ([decoder containsValueForKey:@"MCPinverseRelation"]) { + [self setInverseRelation:[decoder decodeObjectForKey:@"MCPinverseRelation"]]; + } + else { + [self setInverseRelation:nil]; + } + [self setOrigin:[decoder decodeObjectForKey:@"MCPorigin"]]; + [self setDestination:[decoder decodeObjectForKey:@"MCPdestination"]]; +// [self setJoins:[decoder decodeObjectForKey:@"MCPjoins"]]; + joins = [[NSMutableArray alloc] initWithArray:[decoder decodeObjectForKey:@"MCPjoins"]]; + [self setIsToMany:[decoder decodeBoolForKey:@"MCPisToMany"]]; + [self setIsMandatory:[decoder decodeBoolForKey:@"MCPisMandatory"]]; + [self setOwnsDestintation:[decoder decodeBoolForKey:@"MCPownsDestination"]]; + } + + return self; +} + +- (void) encodeWithCoder:(NSCoder *) encoder +{ + if (! [encoder allowsKeyedCoding]) { + NSLog(@"In MCPRelation -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!"); + return; + } + [encoder encodeObject:[self name] forKey:@"MCPname"]; + [encoder encodeInt32:(int32_t)[self deleteRule] forKey:@"MCPdeleteRule"]; + if ([self inverseRelation]) { + [encoder encodeObject:[self inverseRelation] forKey:@"MCPinverseRelation"]; + } + [encoder encodeObject:[self origin] forKey:@"MCPorigin"]; + [encoder encodeObject:[self destination] forKey:@"MCPdestination"]; + [encoder encodeObject:[self joins] forKey:@"MCPjoins"]; + [encoder encodeBool:[self isToMany] forKey:@"MCPisToMany"]; + [encoder encodeBool:[self isMandatory] forKey:@"MCPisMandatory"]; + [encoder encodeBool:[self ownsDestination] forKey:@"MCPownsDestination"]; +} + +#pragma mark Making new joins +/* +- (MCPJoin *) addNewJoin // Usefull for the interface, to be able to create a new join by just using a binding. +{ + [self addJoinFrom:[origin objectInAttributesAtIndex:0] to:[destination objectInAttributesAtIndex:0]]; +} +*/ + +- (MCPJoin *) addJoinFrom:(MCPAttribute *) iFrom to:(MCPAttribute *) iTo +{ + MCPJoin *theJoin; + + if ([iFrom classDescription] != [self origin]) { + NSLog(@"Tried to make a join starting from an attribute (%@) that does NOT belong to the origin class description (%@)! Will not perform the link", iFrom, [self origin]); + return nil; + } + if ([iTo classDescription] != [self destination]) { + NSLog(@"Tried to make a join arriving to an attribute (%@) that does NOT belong to the destination class description (%@)! Will not perform the link", iTo, [self destination]); + return nil; + } + theJoin = [[MCPJoin alloc] initForRelation:self from:iFrom to:iTo]; +// theJoin = [[MCPJoin alloc] initFrom:iFrom to:iTo]; +// [joins addObject:theJoin]; + [joins insertObject:theJoin atIndex:[joins count]]; + [theJoin release]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + return theJoin; +} + +- (void) removeJoinFrom:(MCPAttribute *) iFrom to:(MCPAttribute *) iTo +{ + NSDictionary *theJoinDict = [[NSDictionary alloc] initWithObjectsAndKeys:self, @"relation", iFrom, @"origin", iTo, @"destination", nil]; + unsigned int i = [joins indexOfObject:theJoinDict]; + + if (NSNotFound != i) { + [[self objectInJoinsAtIndex:i] invalidate]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + } + [theJoinDict release]; +} + +/* +- (void) unjoinAttribute:(MCPAttribute *) iAttribute +{ + unsigned int i = 0; + +#warning HAVE to rewrite this code!!! + if ([[iAttribute classDescription] isEqual:origin]) { + for (i=0; ([joins count] != i) && ([iAttribute isEqual:[(MCPJoin *)[joins objectAtIndex:i] origin]]); ++i) { + } + } + if ([[iAttribute classDescription] isEqual:destination]) { + for (i=0; ([joins count] != i) && ([iAttribute isEqual:[(MCPJoin *)[joins objectAtIndex:i] destination]]); ++i) { + } + } + if ((0 == i) || ([joins count] == i)) { // No joins found using this attribute. + return; + } + [self removeJoinFrom:[(MCPJoin *)[joins objectAtIndex:i] origin] to:[(MCPJoin *)[joins objectAtIndex:i] destination]]; +} +*/ + +#pragma mark Setters +- (void) setDestination:(MCPClassDescription *) iDestination +{ + if (iDestination != destination) { + [destination removeObjectFromIncomingsAtIndex:[[destination incomings] indexOfObjectIdenticalTo:self]]; + destination = iDestination; + [destination insertObject:self inIncomingsAtIndex:0]; + [self invalidateJoins]; + } +} + + +- (void) setName:(NSString *) iName +{ + if (iName != name) { + [name release]; + name = [iName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + } +} + +- (void) setDeleteRule:(MCPRelationDeleteRule) iDeleteRule +{ + if (iDeleteRule != deleteRule) { // Don't do the notification for nothing!!! + deleteRule = iDeleteRule; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + } +} + +- (void) setInverseRelation:(MCPRelation *) iInverseRelation +{ + if (iInverseRelation != inverseRelation) { + [inverseRelation release]; + inverseRelation = [iInverseRelation retain]; + [inverseRelation setInverseRelation:self]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + } +} + +- (void) insertObject:(MCPJoin *) iJoin inJoinsAtIndex:(unsigned int) index +{ + [joins insertObject:iJoin atIndex:index]; +} + +- (void) removeObjectFromJoinsAtIndex:(unsigned int) index +{ + [joins removeObjectAtIndex:index]; +} + +- (void) setIsToMany:(BOOL) iIsToMany +{ + if (iIsToMany != isToMany) { // Don't do the notification for nothing!!! + isToMany = iIsToMany; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + } +} + +- (void) setIsMandatory:(BOOL) iIsMandatory +{ + if (iIsMandatory != isMandatory) { // Don't do the notification for nothing!!! + isMandatory = iIsMandatory; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + } +} + +- (void) setOwnsDestintation:(BOOL) iOwnsDestination +{ + if (iOwnsDestination != ownsDestination) { // Don't do the notification for nothing!!! + ownsDestination = iOwnsDestination; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + } +} + +#pragma mark Getters +- (NSString *) name +{ + return name; +} + +- (MCPRelationDeleteRule) deleteRule +{ + return deleteRule; +} + +- (MCPRelation *) inverseRelation +{ + return inverseRelation; +} + +- (MCPClassDescription *) origin +{ + return origin; +} + +- (MCPClassDescription *) destination +{ + return destination; +} + +- (NSArray *) joins +{ + return [NSArray arrayWithArray:joins]; +} + +- (unsigned int) countOfJoins +{ + return [joins count]; +} + +- (MCPJoin *) objectInJoinsAtIndex:(unsigned int) index +{ + return (MCPJoin *)((NSNotFound != index) ? [joins objectAtIndex:index] : nil); +} + +- (unsigned int) indexOfJoinIdenticalTo:(id) iJoin +{ + return [joins indexOfObjectIdenticalTo:iJoin]; +} + +- (BOOL) isToMany +{ + return isToMany; +} + +- (BOOL) isMandatory +{ + return isMandatory; +} + +- (BOOL) ownsDestination +{ + return ownsDestination; +} + +#pragma mark Some Usefull methods + +- (MCPAttribute *) destinationAttributeForOrigin:(MCPAttribute *) iFrom +{ + unsigned int i; + + for (i=0; ([joins count] != i) && ([[(MCPJoin *)[joins objectAtIndex:i] origin] isEqual:iFrom]); ++i) { + } + return ([joins count] == i) ? nil : [(MCPJoin *)[joins objectAtIndex:i] destination]; +} + +- (MCPAttribute *) originAttributeForDestination:(MCPAttribute *) iTo +{ + unsigned int i; + + for (i=0; ([joins count] != i) && ([[(MCPJoin *)[joins objectAtIndex:i] destination] isEqual:iTo]); ++i) { + } + return ([joins count] == i) ? nil : [(MCPJoin *)[joins objectAtIndex:i] origin]; +} + + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject +// Equal to another relation, if they have the same name and same origin and destination class descriptions (they have the same names). +// Equal to a string (NSString), if the name of the relation is equal to the string. +{ + if ([iObject isKindOfClass:[MCPRelation class]]) { + MCPRelation *theRelation = (MCPRelation *) iObject; + + return ([name isEqualToString:[theRelation name]]) && ([[self origin] isEqual:[theRelation origin]]) && ([[self destination] isEqual:[theRelation destination]]); + } + if ([iObject isKindOfClass:[NSString class]]) { + return [name isEqualToString:(NSString *)iObject]; + } + return NO; +} + +- (NSString *) descriptionWithLocale:(NSDictionary *) locale +{ + NSMutableString *theRet = [NSMutableString stringWithFormat:@"MCPRelation named %@, going from %@ to %@. Joins :\n", name, [origin name], [destination name]]; + unsigned int i; + + for (i = 0; [joins count] != i; ++i) { + MCPJoin *tmpJoin = (MCPJoin *)[joins objectAtIndex:i]; + [theRet appendFormat:@"\t\t%@ == %@\n", [[tmpJoin origin] name], [[tmpJoin destination] name]]; + } + return theRet; +} + + +#pragma mark For debugging the retain counting +- (id) retain +{ + [super retain]; + return self; +} + +- (void) release +{ + [super release]; + return; +} + +@end + +@implementation MCPRelation (Private) + +#pragma mark Making some work +- (void) invalidateJoins +{ + while ([joins count]) { + [[self objectInJoinsAtIndex:0] invalidate]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; +} + +#pragma mark Setters +- (void) setOrigin:(MCPClassDescription *) iOrigin +{ + if (iOrigin != origin) { + [origin removeObjectFromRelationsAtIndex:[[origin relations] indexOfObjectIdenticalTo:self]]; + origin = iOrigin; + [origin insertObject:self inRelationsAtIndex:[origin countOfRelations]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + [self invalidateJoins]; + } +} + +/* +- (void) setJoins:(NSArray *) iJoins +{ + if (iJoins != joins) { + unsigned int i; + if (joins) { + [self invalidateJoins]; + } + else { + joins = [[NSMutableArray alloc] init]; + } + for (i=0; [iJoins count] != i; ++i) { + [self addJoinFrom:[(MCPJoin *)[iJoins objectAtIndex:i] origin] to:[(MCPJoin *)[iJoins objectAtIndex:i] destination]]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self]; + } +} +*/ + +#pragma mark Getters +- (MCPModel *) model +{ + return [origin model]; +} + +#pragma mark Fro the controller layer and the UI +- (void) addNewDefaultJoin // Usefull for the interface, to be able to create a new join by just using a binding. +{ + [self addJoinFrom:[origin objectInAttributesAtIndex:0] to:[destination objectInAttributesAtIndex:0]]; +} + +@end |