mirror of
https://github.com/gnustep/libs-gdl2.git
synced 2025-02-15 16:10:46 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gdl2/trunk@36234 72102866-910b-0410-8b05-ffd578937521
6518 lines
184 KiB
Objective-C
6518 lines
184 KiB
Objective-C
/**
|
|
EODatabaseContext.m <title>EODatabaseContext Class</title>
|
|
|
|
Copyright (C) 2000-2002,2003,2004,2005 Free Software Foundation, Inc.
|
|
|
|
Author: Mirko Viviani <mirko.viviani@gmail.com>
|
|
Date: June 2000
|
|
|
|
Author: Manuel Guesdon <mguesdon@orange-concept.com>
|
|
Date: October 2000
|
|
|
|
$Revision$
|
|
$Date$
|
|
|
|
<abstract></abstract>
|
|
|
|
This file is part of the GNUstep Database Library.
|
|
|
|
<license>
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 3 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public
|
|
License along with this library; see the file COPYING.LIB.
|
|
If not, write to the Free Software Foundation,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
</license>
|
|
**/
|
|
|
|
#include "config.h"
|
|
|
|
RCS_ID("$Id$")
|
|
|
|
#ifdef GNUSTEP
|
|
#include <Foundation/NSArray.h>
|
|
#include <Foundation/NSDictionary.h>
|
|
#include <Foundation/NSString.h>
|
|
#include <Foundation/NSException.h>
|
|
#include <Foundation/NSValue.h>
|
|
#include <Foundation/NSZone.h>
|
|
#include <Foundation/NSNotification.h>
|
|
#include <Foundation/NSSet.h>
|
|
#include <Foundation/NSData.h>
|
|
#include <Foundation/NSKeyValueCoding.h>
|
|
#include <Foundation/NSDebug.h>
|
|
#else
|
|
#include <Foundation/Foundation.h>
|
|
#endif
|
|
|
|
#ifndef GNUSTEP
|
|
#include <GNUstepBase/GNUstep.h>
|
|
#include <GNUstepBase/NSDebug+GNUstepBase.h>
|
|
#include <GNUstepBase/NSObject+GNUstepBase.h>
|
|
#endif
|
|
|
|
#include <GNUstepBase/GSObjCRuntime.h>
|
|
|
|
#include <EOControl/EOFault.h>
|
|
#include <EOControl/EOEditingContext.h>
|
|
#include <EOControl/EOClassDescription.h>
|
|
#include <EOControl/EOGenericRecord.h>
|
|
#include <EOControl/EOQualifier.h>
|
|
#include <EOControl/EOKeyGlobalID.h>
|
|
#include <EOControl/EOFetchSpecification.h>
|
|
#include <EOControl/EOSortOrdering.h>
|
|
#include <EOControl/EOKeyValueCoding.h>
|
|
#include <EOControl/EOMutableKnownKeyDictionary.h>
|
|
#include <EOControl/EOCheapArray.h>
|
|
#include <EOControl/EONSAddOns.h>
|
|
#include <EOControl/EONull.h>
|
|
#include <EOControl/EODebug.h>
|
|
|
|
#include <EOAccess/EOAdaptor.h>
|
|
#include <EOAccess/EOAdaptorChannel.h>
|
|
#include <EOAccess/EOAdaptorContext.h>
|
|
#include <EOAccess/EOModel.h>
|
|
#include <EOAccess/EOModelGroup.h>
|
|
#include <EOAccess/EOEntity.h>
|
|
#include <EOAccess/EORelationship.h>
|
|
#include <EOAccess/EOAttribute.h>
|
|
#include <EOAccess/EOAttributePriv.h>
|
|
#include <EOAccess/EOStoredProcedure.h>
|
|
#include <EOAccess/EOJoin.h>
|
|
|
|
#include <EOAccess/EODatabase.h>
|
|
#include <EOAccess/EODatabaseContext.h>
|
|
#include <EOAccess/EODatabaseChannel.h>
|
|
#include <EOAccess/EODatabaseOperation.h>
|
|
#include <EOAccess/EOAccessFault.h>
|
|
#include <EOAccess/EOExpressionArray.h>
|
|
#include <EOAccess/EOSQLExpression.h>
|
|
|
|
#include "EOPrivate.h"
|
|
#include "EOEntityPriv.h"
|
|
#include "EOAccessFaultPriv.h"
|
|
#include "EODatabaseContextPriv.h"
|
|
|
|
#include <string.h>
|
|
|
|
|
|
#define _LOCK_BUFFER 128
|
|
|
|
|
|
NSString *EODatabaseChannelNeededNotification = @"EODatabaseChannelNeededNotification";
|
|
|
|
NSString *EODatabaseContextKey = @"EODatabaseContextKey";
|
|
NSString *EODatabaseOperationsKey = @"EODatabaseOperationsKey";
|
|
NSString *EOFailedDatabaseOperationKey = @"EOFailedDatabaseOperationKey";
|
|
|
|
NSString *EOCustomQueryExpressionHintKey = @"EOCustomQueryExpressionHintKey";
|
|
NSString *EOStoredProcedureNameHintKey = @"EOStoredProcedureNameHintKey";
|
|
|
|
static BOOL _useToManyCaching = YES;
|
|
|
|
@interface EODatabaseContext(EOObjectStoreSupportPrivate)
|
|
- (id) entityForGlobalID: (EOGlobalID *)globalID;
|
|
@end
|
|
|
|
@implementation EODatabaseContext
|
|
|
|
// Initializing instances
|
|
|
|
static Class _contextClass = Nil;
|
|
|
|
+ (void)initialize
|
|
{
|
|
static BOOL initialized=NO;
|
|
if (!initialized)
|
|
{
|
|
initialized=YES;
|
|
|
|
GDL2_EOAccessPrivateInit();
|
|
|
|
_contextClass = GDL2_EODatabaseContextClass;
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(_registerDatabaseContext:)
|
|
name: EOCooperatingObjectStoreNeeded
|
|
object: nil];
|
|
}
|
|
}
|
|
|
|
+ (EODatabaseContext*)databaseContextWithDatabase: (EODatabase *)database
|
|
{
|
|
return AUTORELEASE([[self alloc] initWithDatabase: database]);
|
|
}
|
|
|
|
+ (void)_registerDatabaseContext:(NSNotification *)notification
|
|
{
|
|
EOObjectStoreCoordinator *coordinator = [notification object];
|
|
EODatabaseContext *dbContext = nil;
|
|
EOModel *model = nil;
|
|
NSString *entityName = nil;
|
|
id keyValue = nil;
|
|
|
|
keyValue = [[notification userInfo] objectForKey: @"globalID"];
|
|
|
|
if (keyValue == nil)
|
|
keyValue = [[notification userInfo] objectForKey: @"fetchSpecification"];
|
|
|
|
if (keyValue == nil)
|
|
keyValue = [[notification userInfo] objectForKey: @"object"];
|
|
|
|
if (keyValue)
|
|
entityName = [keyValue entityName];
|
|
|
|
if (entityName)
|
|
model = [[[EOModelGroup defaultGroup] entityNamed:entityName] model];
|
|
|
|
if (model == nil)
|
|
NSLog(@"%@ -- %@ %@: No model for entity named %@",
|
|
NSStringFromSelector(_cmd),
|
|
NSStringFromClass([self class]),
|
|
self,
|
|
entityName);
|
|
|
|
dbContext = [EODatabaseContext databaseContextWithDatabase:
|
|
[EODatabase databaseWithModel: model]];
|
|
|
|
[coordinator addCooperatingObjectStore:dbContext];
|
|
}
|
|
|
|
/*
|
|
* this exists, see EOF 2.2 Release Notes
|
|
* http://support.apple.com/kb/TA26740?viewlocale=en_US
|
|
*/
|
|
|
|
+ (void) _setUseToManyCaching:(BOOL) yn
|
|
{
|
|
_useToManyCaching = yn;
|
|
}
|
|
|
|
|
|
- (void) registerForAdaptorContextNotifications: (EOAdaptorContext*)adaptorContext
|
|
{
|
|
//OK
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(_beginTransaction)
|
|
name: EOAdaptorContextBeginTransactionNotification
|
|
object: adaptorContext];
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(_commitTransaction)
|
|
name: EOAdaptorContextCommitTransactionNotification
|
|
object: adaptorContext];
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(_rollbackTransaction)
|
|
name: EOAdaptorContextRollbackTransactionNotification
|
|
object: adaptorContext];
|
|
}
|
|
|
|
- (id) initWithDatabase: (EODatabase *)database
|
|
{
|
|
if ((self = [self init]))
|
|
{
|
|
_adaptorContext = RETAIN([[database adaptor] createAdaptorContext]);
|
|
|
|
if (_adaptorContext == nil)
|
|
{
|
|
NSLog(@"EODatabaseContext could not create adaptor context");
|
|
AUTORELEASE(self);
|
|
|
|
return nil;
|
|
}
|
|
_database = RETAIN(database);
|
|
|
|
// Register this object into database
|
|
[_database registerContext: self];
|
|
[self setUpdateStrategy: EOUpdateWithOptimisticLocking];
|
|
|
|
_uniqueStack = [NSMutableArray new];
|
|
_deleteStack = [NSMutableArray new];
|
|
_uniqueArrayStack = [NSMutableArray new];
|
|
|
|
_registeredChannels = [NSMutableArray new];
|
|
_batchFaultBuffer = [NSMutableDictionary new];
|
|
_batchToManyFaultBuffer = [NSMutableDictionary new];
|
|
_missingObjectGIDs = [NSMutableSet new];
|
|
|
|
// We want to know when snapshots change in database
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(_snapshotsChangedInDatabase:)
|
|
name: EOObjectsChangedInStoreNotification
|
|
object: _database];
|
|
|
|
// We want to know when objects change
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(_objectsChanged:)
|
|
name: EOObjectsChangedInStoreNotification
|
|
object: self];
|
|
|
|
[self registerForAdaptorContextNotifications: _adaptorContext];
|
|
|
|
//???
|
|
/*NO _snapshots = [NSMutableDictionary new];
|
|
_toManySnapshots = [NSMutableDictionary new];
|
|
*/
|
|
|
|
//NO _lock = [NSRecursiveLock new];
|
|
|
|
|
|
/* //TODO ?
|
|
transactionStackTop = NULL;
|
|
transactionNestingLevel = 0;
|
|
isKeepingSnapshots = YES;
|
|
isUniquingObjects = [database uniquesObjects];
|
|
[database contextDidInit:self];*/
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* Convenience method to check if our delegate handles database exceptions
|
|
* or if we have to do it ourself.
|
|
*/
|
|
|
|
- (BOOL) _delegateHandledDatabaseException:(NSException *) exception
|
|
{
|
|
if (_delegateRespondsTo.shouldHandleDatabaseException)
|
|
{
|
|
|
|
return ([_delegate databaseContext:self
|
|
shouldHandleDatabaseException:exception] == NO);
|
|
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)_snapshotsChangedInDatabase: (NSNotification *)notification
|
|
{
|
|
//OK EOObjectsChangedInStoreNotification EODatabase
|
|
|
|
|
|
if ([notification object] == _database)//??
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName: [notification name]
|
|
object: self
|
|
userInfo: [notification userInfo]];//==> _objectsChanged
|
|
}
|
|
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver: self];
|
|
|
|
[_database unregisterContext: self];
|
|
|
|
DESTROY(_adaptorContext);
|
|
DESTROY(_database);
|
|
|
|
if (_dbOperationsByGlobalID)
|
|
{
|
|
NSDebugMLog(@"MEMORY: dbOperationsByGlobalID count=%u",
|
|
NSCountMapTable(_dbOperationsByGlobalID));
|
|
NSFreeMapTable(_dbOperationsByGlobalID);
|
|
_dbOperationsByGlobalID = NULL;
|
|
}
|
|
/*NO
|
|
DESTROY(_snapshots);
|
|
DESTROY(_toManySnapshots);
|
|
*/
|
|
DESTROY(_uniqueStack);
|
|
DESTROY(_deleteStack);
|
|
DESTROY(_uniqueArrayStack);
|
|
|
|
DESTROY(_registeredChannels);
|
|
|
|
DESTROY(_batchFaultBuffer);
|
|
DESTROY(_batchToManyFaultBuffer);
|
|
DESTROY(_missingObjectGIDs);
|
|
|
|
DESTROY(_lastEntity);
|
|
|
|
if (_nonPrimaryKeyGenerators)
|
|
{
|
|
NSDebugMLog(@"MEMORY: nonPrimaryKeyGnerators count=%u",
|
|
NSCountHashTable(_nonPrimaryKeyGenerators));
|
|
|
|
NSFreeHashTable(_nonPrimaryKeyGenerators);
|
|
_nonPrimaryKeyGenerators = NULL;
|
|
}
|
|
|
|
if (_lockedObjects)
|
|
{
|
|
NSResetHashTable(_lockedObjects);
|
|
}
|
|
|
|
DESTROY(_lock);
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
+ (EODatabaseContext *)registeredDatabaseContextForModel: (EOModel *)model
|
|
editingContext: (EOEditingContext *)editingContext
|
|
{
|
|
EOObjectStoreCoordinator *edObjectStore;
|
|
NSArray *cooperatingObjectStores;
|
|
NSEnumerator *storeEnum;
|
|
EOCooperatingObjectStore *coObjectStore;
|
|
EODatabase *anDatabase;
|
|
NSArray *models;
|
|
EODatabaseContext *dbContext = nil;
|
|
|
|
|
|
|
|
if (model && editingContext)
|
|
{
|
|
IMP enumNO=NULL; // nextObject
|
|
edObjectStore = (EOObjectStoreCoordinator *)[editingContext rootObjectStore];
|
|
cooperatingObjectStores = [edObjectStore cooperatingObjectStores]; // get all EODatabaseContexts
|
|
|
|
storeEnum = [cooperatingObjectStores objectEnumerator];
|
|
|
|
while ((coObjectStore = GDL2_NextObjectWithImpPtr(storeEnum,&enumNO)))
|
|
{
|
|
if ([coObjectStore isKindOfClass: [EODatabaseContext class]])
|
|
{
|
|
anDatabase = [(EODatabaseContext *)coObjectStore database];
|
|
|
|
if (anDatabase && (models = [anDatabase models]))
|
|
{
|
|
if ([models containsObject: model])
|
|
{
|
|
dbContext = (EODatabaseContext *)coObjectStore;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!dbContext)
|
|
{
|
|
// no EODatabaseContext found, create a new one
|
|
dbContext = [EODatabaseContext databaseContextWithDatabase:
|
|
[EODatabase databaseWithModel:
|
|
model]];
|
|
|
|
if (dbContext)
|
|
{
|
|
[edObjectStore addCooperatingObjectStore: dbContext];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return dbContext;
|
|
}
|
|
|
|
|
|
+ (Class)contextClassToRegister
|
|
{
|
|
NSEmitTODO();
|
|
// TODO;
|
|
return _contextClass;
|
|
}
|
|
|
|
+ (void)setContextClassToRegister: (Class)contextClass
|
|
{
|
|
_contextClass = contextClass;
|
|
}
|
|
|
|
/** Returns YES if we have at least one busy channel **/
|
|
- (BOOL)hasBusyChannels
|
|
{
|
|
BOOL busy = NO;
|
|
NSUInteger count = 0;
|
|
|
|
count = [_registeredChannels count];
|
|
|
|
if (count>0)
|
|
{
|
|
NSUInteger i = 0;
|
|
IMP oaiIMP=[_registeredChannels methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i = 0 ; !busy && i < count; i++)
|
|
{
|
|
EODatabaseChannel *channel = GDL2_ObjectAtIndexWithImp(_registeredChannels,oaiIMP,i);
|
|
|
|
busy = [channel isFetchInProgress];
|
|
}
|
|
};
|
|
|
|
return busy;
|
|
}
|
|
|
|
- (NSArray *)registeredChannels
|
|
{
|
|
return _registeredChannels;
|
|
}
|
|
|
|
/*
|
|
Adds channel to the pool of available channels used to service database requests.
|
|
Registered channels are retained by the receiver.
|
|
*/
|
|
|
|
- (void)registerChannel: (EODatabaseChannel *)channel
|
|
{
|
|
//call channel databaseContext
|
|
//test if not exists _registeredChannels indexOfObjectIdenticalTo:channel
|
|
NSDebugLog(@"** REGISTER channel ** debug:%d ** total registered:%"PRIuPTR,
|
|
[[channel adaptorChannel] isDebugEnabled],
|
|
[_registeredChannels count] + 1);
|
|
|
|
[_registeredChannels addObject:channel];
|
|
[channel setDelegate: nil];
|
|
}
|
|
|
|
- (void)unregisterChannel: (EODatabaseChannel *)channel
|
|
{
|
|
[_registeredChannels removeObjectIdenticalTo:channel];
|
|
}
|
|
|
|
/** returns a non busy channel if any, nil otherwise **/
|
|
-(EODatabaseChannel *)_availableChannelFromRegisteredChannels
|
|
{
|
|
NSEnumerator *channelsEnum;
|
|
EODatabaseChannel *channel = nil;
|
|
IMP enumNO=NULL; // nextObject
|
|
|
|
channelsEnum = [_registeredChannels objectEnumerator];
|
|
|
|
while ((channel = GDL2_NextObjectWithImpPtr(channelsEnum,&enumNO)))
|
|
{
|
|
if ([channel isFetchInProgress] == NO)
|
|
{
|
|
return channel;
|
|
}
|
|
else
|
|
{
|
|
// NSDebugMLLog(@"EODatabaseContext",@"CHANNEL %p is busy",
|
|
// [channel nonretainedObjectValue]);
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
/** return a non busy channel **/
|
|
- (EODatabaseChannel *)availableChannel
|
|
{
|
|
EODatabaseChannel *channel = nil;
|
|
NSUInteger num = 2;
|
|
|
|
while (!channel && num)
|
|
{
|
|
channel = [self _availableChannelFromRegisteredChannels];
|
|
|
|
if (!channel)
|
|
{
|
|
//If not channel and last try: send a EODatabaseChannelNeededNotification notification before this last try
|
|
if (--num)
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName: EODatabaseChannelNeededNotification
|
|
object: self];
|
|
}
|
|
}
|
|
|
|
if ((!channel) && ([_registeredChannels count] < 1)) {
|
|
channel = [EODatabaseChannel databaseChannelWithDatabaseContext: self];
|
|
if (channel)
|
|
{
|
|
[self registerChannel:channel];
|
|
}
|
|
|
|
}
|
|
|
|
return channel;
|
|
}
|
|
|
|
/** returns the database **/
|
|
- (EODatabase *)database
|
|
{
|
|
return _database;
|
|
}
|
|
|
|
- (void) setCoordinator:(EOObjectStoreCoordinator *) newCoordinator
|
|
{
|
|
[super setCoordinator: newCoordinator];
|
|
|
|
// TODO: locking magic.
|
|
}
|
|
|
|
|
|
/** returns the adaptor context **/
|
|
- (EOAdaptorContext *)adaptorContext
|
|
{
|
|
return _adaptorContext;
|
|
}
|
|
|
|
/** Set the update strategy to 'strategy'
|
|
May raise an exception if transaction has began or if you want pessimistic lock when there's already a snapshot recorded
|
|
**/
|
|
- (void)setUpdateStrategy: (EOUpdateStrategy)strategy
|
|
{
|
|
if (_flags.beganTransaction)
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"%@ -- %@ 0x%x: transaction in progress",
|
|
NSStringFromSelector(_cmd),
|
|
NSStringFromClass([self class]),
|
|
self];
|
|
|
|
//Can't set pessimistic locking where there's already snapshosts !
|
|
if (strategy == EOUpdateWithPessimisticLocking
|
|
&& [[_database snapshots] count])
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"%@ -- %@ 0x%x: can't set EOUpdateWithPessimisticLocking when receive's EODatabase already has snapshots",
|
|
NSStringFromSelector(_cmd),
|
|
NSStringFromClass([self class]),
|
|
self];
|
|
|
|
_updateStrategy = strategy;
|
|
}
|
|
|
|
/** Get the update strategy **/
|
|
- (EOUpdateStrategy)updateStrategy
|
|
{
|
|
return _updateStrategy;
|
|
}
|
|
|
|
/** Get the delegate **/
|
|
- (id)delegate
|
|
{
|
|
return _delegate;
|
|
}
|
|
|
|
/** Set the delegate **/
|
|
- (void)setDelegate:(id)delegate
|
|
{
|
|
NSEnumerator *channelsEnum = [_registeredChannels objectEnumerator];
|
|
EODatabaseChannel *channel = nil;
|
|
IMP enumNO=NULL; // nextObject
|
|
|
|
_delegate = delegate;
|
|
|
|
_delegateRespondsTo.willRunLoginPanelToOpenDatabaseChannel =
|
|
[delegate respondsToSelector: @selector(databaseContext:willRunLoginPanelToOpenDatabaseChannel:)];
|
|
_delegateRespondsTo.newPrimaryKey =
|
|
[delegate respondsToSelector: @selector(databaseContext:newPrimaryKeyForObject:entity:)];
|
|
_delegateRespondsTo.willPerformAdaptorOperations =
|
|
[delegate respondsToSelector: @selector(databaseContext:willPerformAdaptorOperations:adaptorChannel:)];
|
|
_delegateRespondsTo.shouldInvalidateObject =
|
|
[delegate respondsToSelector: @selector(databaseContext:shouldInvalidateObjectWithGlobalID:snapshot:)];
|
|
_delegateRespondsTo.willOrderAdaptorOperations =
|
|
[delegate respondsToSelector: @selector(databaseContext:willOrderAdaptorOperationsFromDatabaseOperations:)];
|
|
_delegateRespondsTo.shouldLockObject =
|
|
[delegate respondsToSelector: @selector(databaseContext:shouldLockObjectWithGlobalID:snapshot:)];
|
|
_delegateRespondsTo.shouldRaiseForLockFailure =
|
|
[delegate respondsToSelector: @selector(databaseContext:shouldRaiseExceptionForLockFailure:)];
|
|
_delegateRespondsTo.shouldFetchObjects =
|
|
[delegate respondsToSelector: @selector(databaseContext:shouldFetchObjectsWithFetchSpecification:editingContext:)];
|
|
_delegateRespondsTo.didFetchObjects =
|
|
[delegate respondsToSelector: @selector(databaseContext:didFetchObjects:fetchSpecification:editingContext:)];
|
|
_delegateRespondsTo.shouldFetchObjectFault =
|
|
[delegate respondsToSelector: @selector(databaseContext:shouldFetchObjectsWithFetchSpecification:editingContext:)];
|
|
_delegateRespondsTo.shouldFetchArrayFault =
|
|
[delegate respondsToSelector: @selector(databaseContext:shouldFetchArrayFault:)];
|
|
_delegateRespondsTo.shouldHandleDatabaseException =
|
|
[delegate respondsToSelector: @selector(databaseContext:shouldHandleDatabaseException:)];
|
|
_delegateRespondsTo.databaseContextFailedToFetchObject =
|
|
[delegate respondsToSelector: @selector(databaseContext:failedToFetchObject:globalID:)];
|
|
|
|
|
|
while ((channel = GDL2_NextObjectWithImpPtr(channelsEnum,&enumNO)))
|
|
[channel setDelegate: delegate];
|
|
}
|
|
|
|
- (void)handleDroppedConnection
|
|
{
|
|
DESTROY(_adaptorContext);
|
|
|
|
DESTROY(_registeredChannels);
|
|
|
|
_adaptorContext = RETAIN([[[self database] adaptor] createAdaptorContext]);
|
|
_registeredChannels = [NSMutableArray new];
|
|
|
|
}
|
|
|
|
- (NSArray *) missingObjectGlobalIDs
|
|
{
|
|
return [_missingObjectGIDs allObjects];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation EODatabaseContext (EOObjectStoreSupport)
|
|
|
|
/** Return a fault for row 'row' **/
|
|
- (id)faultForRawRow: (NSDictionary *)row
|
|
entityNamed: (NSString *)entityName
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
EOEntity *entity;
|
|
EOGlobalID *gid;
|
|
id object;
|
|
|
|
|
|
|
|
entity = [_database entityNamed: entityName];
|
|
gid = [entity globalIDForRow: row];
|
|
|
|
object = [self faultForGlobalID: gid
|
|
editingContext: context];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"object=%p of class (%@)",
|
|
object, [object class]);
|
|
|
|
|
|
|
|
return object;
|
|
}
|
|
|
|
/** return entity corresponding to 'globalID' **/
|
|
- (id) entityForGlobalID: (EOGlobalID *)globalID
|
|
{
|
|
NSString *entityName;
|
|
|
|
entityName = [(EOKeyGlobalID *)globalID entityName];
|
|
|
|
if ((_lastEntity) && (entityName == [_lastEntity name]))
|
|
{
|
|
return _lastEntity;
|
|
}
|
|
|
|
ASSIGN(_lastEntity, [_database entityNamed: entityName]);
|
|
|
|
return _lastEntity;
|
|
}
|
|
|
|
/** Make object a fault **/
|
|
- (void) _turnToFault: (id)object
|
|
gid: (EOGlobalID *)globalID
|
|
editingContext: (EOEditingContext *)context
|
|
isComplete: (BOOL)isComplete
|
|
{
|
|
//OK
|
|
EOAccessFaultHandler *handler;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSAssert(globalID, @"No globalID");
|
|
NSAssert1([globalID isKindOfClass: [EOKeyGlobalID class]],
|
|
@"globalID is not a EOKeyGlobalID but a %@",
|
|
[globalID class]);
|
|
|
|
if ([(EOKeyGlobalID*)globalID areKeysAllNulls])
|
|
NSWarnLog(@"All key of globalID %p (%@) are nulls",
|
|
globalID,
|
|
globalID);
|
|
|
|
handler = [EOAccessFaultHandler
|
|
accessFaultHandlerWithGlobalID: (EOKeyGlobalID*)globalID
|
|
databaseContext: self
|
|
editingContext: context];
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"object->class_pointer=%p",
|
|
GSObjCClass(object));
|
|
|
|
[EOFault makeObjectIntoFault: object
|
|
withHandler: handler];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"object->class_pointer=%p",
|
|
GSObjCClass(object));
|
|
|
|
[self _addBatchForGlobalID: (EOKeyGlobalID*)globalID
|
|
fault: object];
|
|
|
|
|
|
//TODO: use isComplete
|
|
}
|
|
|
|
/** Get a fault for 'globalID' **/
|
|
- (id)faultForGlobalID: (EOGlobalID *)globalID
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
//Seems OK
|
|
EOClassDescription *classDescription = nil;
|
|
EOEntity *entity;
|
|
id object = nil;
|
|
BOOL isFinal;
|
|
|
|
|
|
|
|
|
|
|
|
isFinal = [(EOKeyGlobalID *)globalID isFinal];
|
|
entity = [self entityForGlobalID: globalID];
|
|
|
|
NSAssert(entity, @"no entity");
|
|
|
|
classDescription = [entity classDescriptionForInstances];
|
|
|
|
|
|
|
|
object = [classDescription createInstanceWithEditingContext: context
|
|
globalID: globalID
|
|
zone: NULL];
|
|
|
|
NSAssert1(object, @"No Object. classDescription=%@", classDescription);
|
|
/*mirko: NO
|
|
NSDictionary *pk;
|
|
NSEnumerator *pkEnum;
|
|
NSString *pkKey;
|
|
NSArray *classPropertyNames;
|
|
classPropertyNames = [entity classPropertyNames];
|
|
pk = [entity primaryKeyForGlobalID:(EOKeyGlobalID *)globalID];
|
|
pkEnum = [pk keyEnumerator];
|
|
while ((pkKey = [pkEnum nextObject]))
|
|
{
|
|
if ([classPropertyNames containsObject:pkKey] == YES)
|
|
[obj takeStoredValue:[pk objectForKey:pkKey]
|
|
forKey:pkKey];
|
|
}
|
|
*/
|
|
|
|
|
|
if ([(EOKeyGlobalID *)globalID areKeysAllNulls])
|
|
NSWarnLog(@"All key of globalID %p (%@) are nulls",
|
|
globalID,
|
|
globalID);
|
|
|
|
[self _turnToFault: object
|
|
gid: globalID
|
|
editingContext: context
|
|
isComplete: isFinal];//??
|
|
|
|
|
|
|
|
EOEditingContext_recordObjectGlobalIDWithImpPtr(context,NULL,object,globalID);
|
|
|
|
|
|
|
|
return object;
|
|
}
|
|
|
|
/** Get an array fault for globalID for relationshipName **/
|
|
- (NSArray *)arrayFaultWithSourceGlobalID: (EOGlobalID *)globalID
|
|
relationshipName: (NSString *)relationshipName
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
//Seems OK
|
|
NSArray *obj = nil;
|
|
|
|
if (![globalID isKindOfClass: [EOKeyGlobalID class]])
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"%@ -- %@ The globalID %@ must be an EOKeyGlobalID to be able to construct a fault",
|
|
NSStringFromSelector(_cmd),
|
|
NSStringFromClass([self class]),
|
|
globalID];
|
|
}
|
|
else
|
|
{
|
|
EOAccessArrayFaultHandler *handler = nil;
|
|
|
|
obj = [EOCheapCopyMutableArray array];
|
|
handler = [EOAccessArrayFaultHandler
|
|
accessArrayFaultHandlerWithSourceGlobalID:
|
|
(EOKeyGlobalID*)globalID
|
|
relationshipName: relationshipName
|
|
databaseContext: self
|
|
editingContext: context];
|
|
|
|
[EOFault makeObjectIntoFault: obj
|
|
withHandler: handler];
|
|
|
|
[self _addToManyBatchForSourceGlobalID: (EOKeyGlobalID *)globalID
|
|
relationshipName: relationshipName
|
|
fault: (EOFault*)obj];
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
- (void)initializeObject: (id)object
|
|
withGlobalID: (EOGlobalID *)globalID
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
NSDictionary * snapDict = nil;
|
|
EOEntity * entity = nil;
|
|
|
|
/*
|
|
TODO use this stuff -- dw
|
|
if (globalID == _currentGlobalID)
|
|
{
|
|
snapDict = _currentSnapshot;
|
|
entity = _lastEntity;
|
|
} else ...
|
|
*/
|
|
|
|
if ([globalID isTemporary])
|
|
{
|
|
return;
|
|
}
|
|
|
|
snapDict = [self snapshotForGlobalID:globalID];
|
|
|
|
if ([(EOKeyGlobalID *)globalID isFinal])
|
|
{
|
|
entity = [self entityForGlobalID:globalID];
|
|
} else {
|
|
object = [context objectForGlobalID:globalID];
|
|
if (!object)
|
|
{
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"%s No object for gid %@ in %@", __PRETTY_FUNCTION__, globalID, context];
|
|
}
|
|
entity = [_database entityForObject:object];
|
|
}
|
|
|
|
if (!snapDict)
|
|
{
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"%s No snapshot for gid %@", __PRETTY_FUNCTION__, globalID];
|
|
} else {
|
|
|
|
[self initializeObject: object
|
|
row: snapDict
|
|
entity: entity
|
|
editingContext: context];
|
|
|
|
[_database incrementSnapshotCountForGlobalID:globalID];
|
|
}
|
|
|
|
}
|
|
|
|
- (void) _objectsChanged: (NSNotification*)notification
|
|
{
|
|
/*object==self EOObjectsChangedInStoreNotification
|
|
userInfo = {
|
|
deleted = (List Of GlobalIDs);
|
|
inserted = (List Of GlobalIDs);
|
|
updated = (List Of GlobalIDs);
|
|
*/
|
|
|
|
if ([notification object] != self)
|
|
{
|
|
// not sure if that ever happens -- dw
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
else
|
|
{
|
|
NSMutableArray * workArray = [NSMutableArray array];
|
|
NSDictionary * userInfo = [notification userInfo];
|
|
NSArray * myObjects = nil;
|
|
NSUInteger i, count;
|
|
|
|
if ((myObjects = [userInfo objectForKey: EOUpdatedKey]))
|
|
{
|
|
[workArray addObjectsFromArray:myObjects];
|
|
}
|
|
|
|
if ((myObjects = [userInfo objectForKey: EOInsertedKey]))
|
|
{
|
|
[workArray addObjectsFromArray:myObjects];
|
|
}
|
|
|
|
if ((myObjects = [userInfo objectForKey: EODeletedKey]))
|
|
{
|
|
[workArray addObjectsFromArray:myObjects];
|
|
}
|
|
|
|
if ((myObjects = [userInfo objectForKey: EOInvalidatedKey]))
|
|
{
|
|
[workArray addObjectsFromArray:myObjects];
|
|
}
|
|
|
|
count = [workArray count];
|
|
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[workArray methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
EOKeyGlobalID *gid=GDL2_ObjectAtIndexWithImp(workArray,oaiIMP,i);
|
|
NSString *entityName;
|
|
|
|
if ([gid isKindOfClass:[EOKeyGlobalID class]]) {
|
|
entityName = [gid entityName];
|
|
|
|
[_database invalidateResultCacheForEntityNamed: entityName];
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
- (void) _snapshotsChangedInDatabase: (NSNotification*)notification
|
|
{
|
|
|
|
|
|
/*
|
|
userInfo = {
|
|
deleted = (List Of GlobalIDs);
|
|
inserted = (List Of GlobalIDs);
|
|
updated = (List Of GlobalIDs);
|
|
}}
|
|
*/
|
|
|
|
if ([notification object] != self)
|
|
{
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName: EOObjectsChangedInStoreNotification
|
|
object: self
|
|
userInfo: [notification userInfo]];
|
|
//call _objectsChanged: and ObjectStoreCoordinator _objectsChangedInSubStore:
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (NSArray *)objectsForSourceGlobalID: (EOGlobalID *)globalID
|
|
relationshipName: (NSString *)name
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
//Near OK
|
|
NSArray *objects = nil;
|
|
id sourceObjectFault = nil;
|
|
id relationshipValue = nil;
|
|
NSArray *sourceSnapshot = nil;
|
|
NSUInteger sourceSnapshotCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
//First get the id from which we search the source object
|
|
sourceObjectFault = [context faultForGlobalID: globalID
|
|
editingContext: context];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"sourceObjectFault %p=%@",
|
|
sourceObjectFault, sourceObjectFault);
|
|
|
|
// Get the fault value from source object
|
|
|
|
|
|
relationshipValue = [sourceObjectFault storedValueForKey: name];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"relationshipValue %p=%@",
|
|
relationshipValue, relationshipValue);
|
|
|
|
//Try to see if there is a snapshot for the source object
|
|
sourceSnapshot = [_database snapshotForSourceGlobalID: globalID
|
|
relationshipName: name];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"sourceSnapshot %p (%@)=%@",
|
|
sourceSnapshot, [sourceSnapshot class],
|
|
sourceSnapshot);
|
|
|
|
sourceSnapshotCount = [sourceSnapshot count];
|
|
|
|
if (sourceSnapshotCount > 0)
|
|
{
|
|
EOGlobalID *snapGID = nil;
|
|
id snapFault = nil;
|
|
NSUInteger i;
|
|
IMP addObjectIMP=NULL;
|
|
IMP oaiIMP=NULL;
|
|
|
|
[EOFault clearFault: relationshipValue];
|
|
|
|
// Be carefull: Never call methodForSelector before clearing fault !
|
|
addObjectIMP=[relationshipValue methodForSelector:@selector(addObject:)];
|
|
oaiIMP=[sourceSnapshot methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
|
|
for (i = 0; i < sourceSnapshotCount; i++)
|
|
{
|
|
snapGID = GDL2_ObjectAtIndexWithImp(sourceSnapshot,oaiIMP,i);
|
|
|
|
|
|
|
|
snapFault = [context faultForGlobalID: snapGID
|
|
editingContext: context];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"snapFault=%@",
|
|
snapFault);
|
|
|
|
GDL2_AddObjectWithImp(relationshipValue,addObjectIMP,snapFault);
|
|
}
|
|
|
|
objects = relationshipValue;
|
|
}
|
|
else
|
|
{
|
|
EOEntity *entity;
|
|
EORelationship *relationship;
|
|
NSUInteger maxBatch = 0;
|
|
BOOL isToManyToOne = NO;
|
|
EOEntity *destinationEntity = nil;
|
|
EOModel *destinationEntityModel = nil;
|
|
NSArray *models = nil;
|
|
EOQualifier *auxiliaryQualifier = nil;
|
|
NSDictionary *contextSourceSnapshot = nil;
|
|
id sourceObject = nil;
|
|
EORelationship *inverseRelationship = nil;
|
|
EOEntity *invRelEntity = nil;
|
|
NSArray *invRelEntityClassProperties = nil;
|
|
NSString *invRelName = nil;
|
|
EOQualifier *qualifier = nil;
|
|
EOFetchSpecification *fetchSpec = nil;
|
|
|
|
// Get the source object entity
|
|
entity = [self entityForGlobalID: globalID];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"entity name=%@",
|
|
[entity name]);
|
|
|
|
//Get the relationship named 'name'
|
|
relationship = [entity relationshipNamed: name];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"relationship=%@",
|
|
relationship);
|
|
|
|
//Get the max number of fault to fetch
|
|
maxBatch = [relationship numberOfToManyFaultsToBatchFetch];
|
|
|
|
isToManyToOne = [relationship isToManyToOne];//NO
|
|
|
|
if (isToManyToOne)
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO if isToManyToOne
|
|
}
|
|
|
|
//Get the fault entity (aka relationsip destination entity)
|
|
destinationEntity = [relationship destinationEntity];
|
|
NSDebugMLLog(@"EODatabaseContext", @"destinationEntity name=%@",
|
|
[destinationEntity name]);
|
|
|
|
//Get the destination entity model
|
|
destinationEntityModel = [destinationEntity model];
|
|
|
|
//and _database model to verify if the destinationEntityModel is in database models
|
|
models = [_database models];
|
|
|
|
if ([models indexOfObjectIdenticalTo: destinationEntityModel]
|
|
== NSNotFound)
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO error
|
|
}
|
|
|
|
//Get the relationship qualifier if any
|
|
auxiliaryQualifier = [relationship auxiliaryQualifier];//nil
|
|
|
|
if (auxiliaryQualifier)
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO if auxqualif
|
|
}
|
|
|
|
//??
|
|
contextSourceSnapshot = EODatabaseContext_snapshotForGlobalIDWithImpPtr(self,NULL,globalID);
|
|
|
|
//NSEmitTODO();
|
|
//TODO Why first asking for faultForGlobalID and now asking objectForGlobalID ??
|
|
|
|
sourceObject = [context objectForGlobalID: globalID];
|
|
|
|
|
|
inverseRelationship = [relationship inverseRelationship];
|
|
NSDebugMLLog(@"EODatabaseContext", @"inverseRelationship=%@",
|
|
inverseRelationship);
|
|
|
|
if (!inverseRelationship)
|
|
{
|
|
NSEmitTODO();
|
|
//[self notImplemented: _cmd]; //TODO if !inverseRelationship
|
|
inverseRelationship = [relationship hiddenInverseRelationship];
|
|
//VERIFY (don't know if this is the good way)
|
|
}
|
|
|
|
invRelEntity = [inverseRelationship entity];
|
|
invRelEntityClassProperties = [invRelEntity classProperties];
|
|
invRelName = [inverseRelationship name];
|
|
|
|
|
|
|
|
|
|
qualifier = [EOKeyValueQualifier qualifierWithKey: invRelName
|
|
operatorSelector: @selector(isEqualTo:)
|
|
value: sourceObject];
|
|
|
|
|
|
|
|
fetchSpec = [EOFetchSpecification fetchSpecification];
|
|
|
|
[fetchSpec setQualifier: qualifier];
|
|
[fetchSpec setEntityName: [destinationEntity name]];
|
|
|
|
|
|
|
|
objects = [context objectsWithFetchSpecification: fetchSpec
|
|
editingContext: context];
|
|
|
|
[self _registerSnapshot: objects
|
|
forSourceGlobalID: globalID
|
|
relationshipName: name
|
|
editingContext: context];//OK
|
|
}
|
|
|
|
|
|
|
|
|
|
return objects;
|
|
}
|
|
|
|
- (void)_registerSnapshot: (NSArray*)snapshot
|
|
forSourceGlobalID: (EOGlobalID*)globalID
|
|
relationshipName: (NSString*)name
|
|
editingContext: (EOEditingContext*)context
|
|
{
|
|
//OK
|
|
NSArray *gids;
|
|
|
|
|
|
|
|
gids = [context resultsOfPerformingSelector: @selector(globalIDForObject:)
|
|
withEachObjectInArray: snapshot];
|
|
|
|
[_database recordSnapshot: gids
|
|
forSourceGlobalID: globalID
|
|
relationshipName: name];
|
|
|
|
|
|
}
|
|
|
|
- (void)refaultObject: object
|
|
withGlobalID: (EOGlobalID *)globalID
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
|
|
|
|
[EOObserverCenter suppressObserverNotification];
|
|
|
|
NS_DURING
|
|
{
|
|
[object clearProperties];//OK
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
[EOObserverCenter enableObserverNotification];
|
|
|
|
|
|
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER;
|
|
|
|
[EOObserverCenter enableObserverNotification];
|
|
|
|
if ([(EOKeyGlobalID *)globalID areKeysAllNulls])
|
|
NSWarnLog(@"All key of globalID %p (%@) are nulls",
|
|
globalID,
|
|
globalID);
|
|
|
|
[self _turnToFault: object
|
|
gid: globalID
|
|
editingContext: context
|
|
isComplete: YES]; //Why YES ?
|
|
|
|
[self forgetSnapshotForGlobalID:globalID];
|
|
|
|
|
|
}
|
|
|
|
- (void)saveChangesInEditingContext: (EOEditingContext *)context
|
|
{
|
|
//TODO: locks ?
|
|
NSException *exception = nil;
|
|
|
|
|
|
|
|
[self prepareForSaveWithCoordinator: nil
|
|
editingContext: context];
|
|
|
|
[self recordChangesInEditingContext];
|
|
|
|
NS_DURING
|
|
{
|
|
[self performChanges];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSDebugMLog(@"EXCEPTION: %@", localException);
|
|
exception = localException;
|
|
}
|
|
NS_ENDHANDLER;
|
|
|
|
//I don't know if this is really the good place to catch exception and rollback...
|
|
if (exception)
|
|
{
|
|
[self rollbackChanges];
|
|
[exception raise];
|
|
}
|
|
else
|
|
[self commitChanges];
|
|
|
|
|
|
}
|
|
|
|
- (void)_fetchRelationship: (EORelationship *)relationship
|
|
withObjects: (NSArray *)objsArray
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
NSMutableArray *qualArray = nil;
|
|
NSEnumerator *objEnum = nil;
|
|
NSEnumerator *relEnum = nil;
|
|
NSDictionary *snapshot = nil;
|
|
id obj = nil;
|
|
id relObj = nil;
|
|
|
|
|
|
|
|
if ([objsArray count] > 0)
|
|
{
|
|
IMP globalIDForObjectIMP=NULL;
|
|
IMP enumNO=NULL; // nextObject
|
|
|
|
qualArray = [NSMutableArray arrayWithCapacity: 5];
|
|
|
|
if ([relationship isFlattened] == YES)
|
|
{
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"relationship %@ isFlattened", relationship);
|
|
|
|
relEnum = [[relationship componentRelationships] objectEnumerator];
|
|
enumNO=NULL;
|
|
while ((relationship = GDL2_NextObjectWithImpPtr(relEnum,&enumNO)))
|
|
{
|
|
// TODO rebuild object array for relationship path
|
|
|
|
[self _fetchRelationship: relationship
|
|
withObjects: objsArray
|
|
editingContext: context];
|
|
}
|
|
}
|
|
|
|
objEnum = [objsArray objectEnumerator];
|
|
enumNO=NULL;
|
|
while ((obj = GDL2_NextObjectWithImpPtr(objEnum,&enumNO)))
|
|
{
|
|
EOGlobalID* gid=nil;
|
|
relObj = [obj storedValueForKey: [relationship name]];
|
|
gid = EOEditingContext_globalIDForObjectWithImpPtr(context,&globalIDForObjectIMP,relObj);
|
|
snapshot = EODatabaseContext_snapshotForGlobalIDWithImpPtr(self,NULL,gid);
|
|
|
|
[qualArray addObject: [relationship
|
|
qualifierWithSourceRow: snapshot]];
|
|
}
|
|
|
|
[self objectsWithFetchSpecification:
|
|
[EOFetchSpecification
|
|
fetchSpecificationWithEntityName:
|
|
[[relationship destinationEntity] name]
|
|
qualifier: [EOAndQualifier qualifierWithQualifierArray:
|
|
qualArray]
|
|
sortOrderings: nil]
|
|
editingContext: context];
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (NSArray*) _fetchRawRowKeyPaths:(NSArray *) rawRowKeyPaths
|
|
fetchSpecification: (EOFetchSpecification*) fetchSpecification
|
|
entity: (EOEntity *) entity
|
|
editingContext: (EOEditingContext *) context
|
|
{
|
|
EOAdaptorChannel * adaptorChannel = [[self availableChannel] adaptorChannel];
|
|
NSMutableArray * results = [NSMutableArray array];
|
|
NSUInteger fetchLimit = 0;
|
|
NSUInteger rowsFetched = 0;
|
|
NSUInteger keyCount = [rawRowKeyPaths count];
|
|
id messageHandler = nil; // used to prompt the user after the fetch limit is reached.
|
|
NSString * hintKey = nil;
|
|
BOOL continueFetch = NO;
|
|
NSUInteger k;
|
|
EOSQLExpression * expression = nil;
|
|
|
|
NSMutableArray * attributesToFetch;
|
|
if (keyCount == 0)
|
|
{
|
|
// this is an NSMutableArray
|
|
attributesToFetch = (NSMutableArray *) [entity attributesToFetch];
|
|
} else {
|
|
// Populate an array with the attributes we need
|
|
attributesToFetch = [NSMutableArray arrayWithCapacity:keyCount];
|
|
BOOL hasNonFlattenedAttributes = NO;
|
|
|
|
for (k = 0; k < keyCount; k++)
|
|
{
|
|
NSString * keyName = [rawRowKeyPaths objectAtIndex:k];
|
|
EOAttribute * attr = [entity attributeNamed:keyName];
|
|
if (!attr)
|
|
{
|
|
attr = [EOAttribute attributeWithParent:entity
|
|
definition:keyName];
|
|
|
|
} else {
|
|
if ((!hasNonFlattenedAttributes) && (![attr isFlattened]))
|
|
{
|
|
hasNonFlattenedAttributes = YES;
|
|
}
|
|
}
|
|
[attributesToFetch addObject:attr];
|
|
}
|
|
|
|
if (!hasNonFlattenedAttributes)
|
|
{
|
|
// check if lastObject is enouth.
|
|
// the reference however does only checks the lastObject.
|
|
|
|
EOAttribute * attr = [attributesToFetch lastObject];
|
|
EORelationship * relationship;
|
|
|
|
if ([attr isFlattened])
|
|
{
|
|
relationship = [[attr _definitionArray] objectAtIndex:0];
|
|
} else {
|
|
NSString * lastPath = [rawRowKeyPaths lastObject];
|
|
NSString * relName = [[lastPath componentsSeparatedByString:@"."] objectAtIndex:0];
|
|
relationship = [entity relationshipNamed:relName];
|
|
|
|
if ([relationship isFlattened])
|
|
{
|
|
relationship = [[relationship _definitionArray] objectAtIndex:0];
|
|
}
|
|
}
|
|
|
|
EOJoin * join = [[relationship joins] lastObject];
|
|
EOAttribute * attr2 = [join sourceAttribute];
|
|
|
|
[attributesToFetch addObject:attr2];
|
|
}
|
|
// our channel does not support this.
|
|
//[adaptorChannel _setRawDictionaryInitializerForAttributes:attributesToFetch];
|
|
}
|
|
if ((hintKey = [[fetchSpecification hints] objectForKey:@"EOCustomQueryExpressionHintKey"]))
|
|
{
|
|
if ([hintKey isKindOfClass:[NSString class]])
|
|
{
|
|
expression = [[[_adaptorContext adaptor] expressionClass] expressionForString:hintKey];
|
|
} else {
|
|
NSLog(@"%s - %@ is not an NSString but a %@",__PRETTY_FUNCTION__, hintKey, NSStringFromClass([hintKey class]));
|
|
}
|
|
} else {
|
|
EOQualifier * qualifier = [[fetchSpecification qualifier] schemaBasedQualifierWithRootEntity:entity];
|
|
|
|
if (qualifier != [fetchSpecification qualifier])
|
|
{
|
|
[fetchSpecification setQualifier:qualifier];
|
|
}
|
|
}
|
|
if (![adaptorChannel isOpen])
|
|
{
|
|
[adaptorChannel openChannel];
|
|
}
|
|
if (expression)
|
|
{
|
|
[adaptorChannel evaluateExpression:expression];
|
|
[adaptorChannel setAttributesToFetch:attributesToFetch];
|
|
} else {
|
|
[adaptorChannel selectAttributes:attributesToFetch
|
|
fetchSpecification:fetchSpecification
|
|
lock:NO
|
|
entity:entity];
|
|
}
|
|
|
|
// 0 is no fetch limit
|
|
fetchLimit = [fetchSpecification fetchLimit];
|
|
// TODO: check if we need to check for protocol EOMessageHandlers
|
|
if ((fetchLimit > 0) && (([fetchSpecification promptsAfterFetchLimit]) &&
|
|
([context messageHandler])))
|
|
{
|
|
messageHandler = [context messageHandler];
|
|
}
|
|
|
|
|
|
do {
|
|
do {
|
|
NSMutableDictionary * dict = [adaptorChannel fetchRowWithZone:NULL];
|
|
if (!dict) {
|
|
break;
|
|
}
|
|
[results addObject:dict];
|
|
rowsFetched++;
|
|
} while ((fetchLimit == 0) || (rowsFetched < fetchLimit));
|
|
|
|
if (!messageHandler) {
|
|
break;
|
|
}
|
|
|
|
continueFetch = [messageHandler editingContext:context
|
|
shouldContinueFetchingWithCurrentObjectCount:rowsFetched
|
|
originalLimit:fetchLimit
|
|
objectStore:self];
|
|
|
|
} while (continueFetch);
|
|
|
|
[adaptorChannel cancelFetch];
|
|
|
|
if (_delegate)
|
|
{
|
|
|
|
[_delegate databaseContext: self
|
|
didFetchObjects: results
|
|
fetchSpecification: fetchSpecification
|
|
editingContext: context];
|
|
}
|
|
return results;
|
|
}
|
|
|
|
- (void) _populateCacheForFetchSpecification:(EOFetchSpecification *) eofetchspecification
|
|
editingContext:(EOEditingContext *)eoeditingcontext
|
|
{
|
|
NSEmitTODO();
|
|
}
|
|
|
|
- (BOOL) _validateQualifierForEvaluationInMemory:(EOQualifier *) qualifier
|
|
entity:(EOEntity *)entity
|
|
|
|
{
|
|
NSEmitTODO();
|
|
return NO;
|
|
}
|
|
|
|
// _objectsFromEntityCacheWithFetchSpecEditingContext
|
|
- (NSArray*) _objectsFromEntityCacheWithFetchSpec:(EOFetchSpecification*) fetchSpecification
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
NSEmitTODO();
|
|
return nil;
|
|
}
|
|
|
|
- (void) _performPrefetchForFetchSpecification:(EOFetchSpecification*) fetchSpecification
|
|
editingContext:(EOEditingContext *)context
|
|
results:(NSArray*) results
|
|
keyPaths:(NSArray*) prefetchingRelationshipKeyPaths
|
|
{
|
|
NSEmitTODO();
|
|
return;
|
|
}
|
|
|
|
|
|
- (NSArray *)objectsWithFetchSpecification: (EOFetchSpecification *)fetchSpecification
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
id messageHandler = nil;
|
|
EODatabaseChannel *channel = nil;
|
|
NSMutableArray *array = nil;
|
|
NSString *entityName = nil;
|
|
EOEntity *entity = nil;
|
|
NSArray* rawRowKeyPaths = nil;
|
|
NSUInteger fetchLimit=0;
|
|
NSArray * prefetchingRelationshipKeyPaths = nil;
|
|
NSUInteger rowsFetched = 0;
|
|
BOOL continueFetch = NO;
|
|
|
|
channel = [self _obtainOpenChannel];
|
|
|
|
if (_flags.beganTransaction == NO
|
|
&& _updateStrategy == EOUpdateWithPessimisticLocking)
|
|
{
|
|
[_adaptorContext beginTransaction];
|
|
|
|
_flags.beganTransaction = YES;
|
|
}
|
|
|
|
if (_delegateRespondsTo.shouldFetchObjects == YES)
|
|
{
|
|
array = (id)[_delegate databaseContext: self
|
|
shouldFetchObjectsWithFetchSpecification: fetchSpecification
|
|
editingContext: context];
|
|
if (array) {
|
|
return array;
|
|
}
|
|
}
|
|
|
|
entityName = [fetchSpecification entityName];
|
|
entity = [_database entityNamed: entityName];
|
|
|
|
if (!entity)
|
|
{
|
|
return [NSArray array];
|
|
}
|
|
|
|
if ([entity isAbstractEntity] && (![fetchSpecification isDeep]))
|
|
{
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"A FetchSpecification for an abstract entity must be 'deep'! Entity: ",
|
|
entityName];
|
|
}
|
|
|
|
rawRowKeyPaths = [fetchSpecification rawRowKeyPaths];
|
|
if (rawRowKeyPaths)
|
|
{
|
|
NSArray * rawRows = [self _fetchRawRowKeyPaths:rawRowKeyPaths
|
|
fetchSpecification:fetchSpecification
|
|
entity:entity
|
|
editingContext:context];
|
|
return rawRows;
|
|
}
|
|
|
|
if ((!_flags.ignoreEntityCaching) && [entity cachesObjects])
|
|
{
|
|
[self _populateCacheForFetchSpecification:fetchSpecification
|
|
editingContext:context];
|
|
}
|
|
|
|
if (((!_flags.ignoreEntityCaching) && [entity cachesObjects]) &&
|
|
([fetchSpecification isDeep] &&
|
|
[self _validateQualifierForEvaluationInMemory:[fetchSpecification qualifier]
|
|
entity:entity]))
|
|
{
|
|
return [self _objectsFromEntityCacheWithFetchSpec:fetchSpecification
|
|
editingContext:context];
|
|
}
|
|
|
|
array = [NSMutableArray arrayWithCapacity: 8];
|
|
|
|
[channel selectObjectsWithFetchSpecification: fetchSpecification
|
|
editingContext: context];
|
|
|
|
// 0 is no fetch limit. if there is no limit, it makes no sense to ask
|
|
fetchLimit = [fetchSpecification fetchLimit];
|
|
if ((fetchLimit > 0) && (([fetchSpecification promptsAfterFetchLimit]) &&
|
|
([context messageHandler])))
|
|
{
|
|
messageHandler = [context messageHandler];
|
|
}
|
|
|
|
do {
|
|
do {
|
|
id freshObj = [channel fetchObject];
|
|
if (!freshObj) {
|
|
break;
|
|
}
|
|
[array addObject:freshObj];
|
|
rowsFetched++;
|
|
} while ((fetchLimit == 0) || (rowsFetched < fetchLimit));
|
|
|
|
if (!messageHandler) {
|
|
break;
|
|
}
|
|
|
|
continueFetch = [messageHandler editingContext:context
|
|
shouldContinueFetchingWithCurrentObjectCount:rowsFetched
|
|
originalLimit:fetchLimit
|
|
objectStore:self];
|
|
|
|
} while (continueFetch);
|
|
|
|
[channel cancelFetch];
|
|
|
|
prefetchingRelationshipKeyPaths = [fetchSpecification prefetchingRelationshipKeyPaths];
|
|
|
|
if ((prefetchingRelationshipKeyPaths) && ([prefetchingRelationshipKeyPaths count] > 0))
|
|
{
|
|
[self _performPrefetchForFetchSpecification:fetchSpecification
|
|
editingContext:context
|
|
results:array
|
|
keyPaths:prefetchingRelationshipKeyPaths];
|
|
}
|
|
|
|
if (_delegateRespondsTo.didFetchObjects == YES)
|
|
[_delegate databaseContext: self
|
|
didFetchObjects: array
|
|
fetchSpecification: fetchSpecification
|
|
editingContext: context];
|
|
|
|
[channel setCurrentEditingContext:nil];
|
|
|
|
return array;
|
|
}
|
|
|
|
- (BOOL)isObjectLockedWithGlobalID: (EOGlobalID *)gid
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
return [self isObjectLockedWithGlobalID: gid];
|
|
}
|
|
|
|
- (void)lockObjectWithGlobalID: (EOGlobalID *)globalID
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
EOKeyGlobalID *gid = (EOKeyGlobalID *)globalID;
|
|
EOEntity *entity = nil;
|
|
NSDictionary *snapshot;
|
|
EOQualifier *qualifier = nil;
|
|
EOFetchSpecification * fetchspec = nil;
|
|
|
|
snapshot = [self snapshotForGlobalID:gid];
|
|
|
|
if (!snapshot) {
|
|
return;
|
|
}
|
|
|
|
// test if we should continue
|
|
|
|
if (((_delegateRespondsTo.shouldLockObject == YES)) &&
|
|
(([_delegate databaseContext:self
|
|
shouldLockObjectWithGlobalID:gid
|
|
snapshot:snapshot] == NO))) {
|
|
return;
|
|
}
|
|
|
|
NS_DURING {
|
|
NSArray * array = nil;
|
|
|
|
entity = [_database entityNamed: [gid entityName]];
|
|
qualifier = [entity qualifierForPrimaryKey:snapshot];
|
|
|
|
fetchspec = [EOFetchSpecification fetchSpecificationWithEntityName:[entity name]
|
|
qualifier:qualifier
|
|
sortOrderings:nil];
|
|
|
|
[fetchspec setLocksObjects:YES];
|
|
|
|
array = [self objectsWithFetchSpecification:fetchspec
|
|
editingContext:context];
|
|
|
|
if ([array count] != 1) {
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"Failed to lock object with gid %@", gid];
|
|
}
|
|
|
|
|
|
} NS_HANDLER {
|
|
if ((!_delegateRespondsTo.shouldRaiseForLockFailure) ||
|
|
([_delegate databaseContext:self shouldRaiseExceptionForLockFailure:localException])) {
|
|
|
|
[localException raise];
|
|
}
|
|
} NS_ENDHANDLER;
|
|
|
|
}
|
|
|
|
- (void)invalidateAllObjects
|
|
{
|
|
NSDictionary *snapshots;
|
|
NSArray *gids;
|
|
|
|
[_database invalidateResultCache];
|
|
|
|
snapshots = [_database snapshots];
|
|
gids = [snapshots allKeys];
|
|
[self invalidateObjectsWithGlobalIDs: gids];
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName: EOInvalidatedAllObjectsInStoreNotification
|
|
object: self];
|
|
}
|
|
|
|
- (void)invalidateObjectsWithGlobalIDs: (NSArray *)globalIDs
|
|
{
|
|
NSMutableArray *array = nil;
|
|
NSEnumerator *enumerator;
|
|
EOKeyGlobalID *gid;
|
|
|
|
if (_delegateRespondsTo.shouldInvalidateObject == YES)
|
|
{
|
|
IMP enumNO=NULL; // nextObject
|
|
array = [NSMutableArray array];
|
|
enumerator = [globalIDs objectEnumerator];
|
|
|
|
while ((gid = GDL2_NextObjectWithImpPtr(enumerator,&enumNO)))
|
|
{
|
|
if ([_delegate databaseContext: self
|
|
shouldInvalidateObjectWithGlobalID: gid
|
|
snapshot: EODatabaseContext_snapshotForGlobalIDWithImpPtr(self,NULL,gid)] == YES)
|
|
[array addObject: gid];
|
|
}
|
|
}
|
|
|
|
[self forgetSnapshotsForGlobalIDs: ((id)array ? (id)array : globalIDs)];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation EODatabaseContext(EOCooperatingObjectStoreSupport)
|
|
|
|
- (BOOL)ownsGlobalID: (EOGlobalID *)globalID
|
|
{
|
|
if ([globalID isKindOfClass: [EOKeyGlobalID class]] &&
|
|
[_database entityNamed: [(EOKeyGlobalID*) globalID entityName]])
|
|
return YES;
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)ownsObject: (id)object
|
|
{
|
|
if ([_database entityForObject: object])
|
|
return YES;
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)ownsEntityNamed: (NSString *)entityName
|
|
{
|
|
if ([_database entityNamed: entityName])
|
|
return YES;
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)handlesFetchSpecification: (EOFetchSpecification *)fetchSpecification
|
|
{
|
|
//OK
|
|
if ([_database entityNamed: [fetchSpecification entityName]])
|
|
return YES;
|
|
else
|
|
return NO;
|
|
}
|
|
/* //Mirko:
|
|
- (EODatabaseOperation *)_dbOperationWithObject:object
|
|
operator:(EODatabaseOperator)operator
|
|
{
|
|
NSMapEnumerator gidEnum;
|
|
EODatabaseOperation *op;
|
|
EOGlobalID *gid;
|
|
|
|
gidEnum = NSEnumerateMapTable(_dbOperationsByGlobalID);
|
|
while (NSNextMapEnumeratorPair(&gidEnum, (void **)&gid, (void **)&op))
|
|
{
|
|
if ([[op object] isEqual:object] == YES)
|
|
{
|
|
if ([op databaseOperator] == operator)
|
|
return op;
|
|
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (void)_setGlobalID:(EOGlobalID *)globalID
|
|
forDatabaseOperation:(EODatabaseOperation *)op
|
|
{
|
|
EOGlobalID *oldGlobalID = [op globalID];
|
|
|
|
[op _setGlobalID:globalID];
|
|
|
|
NSMapInsert(_dbOperationsByGlobalID, globalID, op);
|
|
NSMapRemove(_dbOperationsByGlobalID, oldGlobalID);
|
|
}
|
|
|
|
- (EODatabaseOperation *)_dbOperationWithGlobalID:(EOGlobalID *)globalID
|
|
object:object
|
|
entity:(EOEntity *)entity
|
|
operator:(EODatabaseOperator)operator
|
|
{
|
|
EODatabaseOperation *op;
|
|
NSMutableDictionary *newRow;
|
|
NSMapEnumerator gidEnum;
|
|
EOAttribute *attribute;
|
|
EOGlobalID *gid;
|
|
NSString *key;
|
|
NSArray *classProperties;
|
|
BOOL found = NO;
|
|
int i, count;
|
|
id val;
|
|
|
|
gidEnum = NSEnumerateMapTable(_dbOperationsByGlobalID);
|
|
while (NSNextMapEnumeratorPair(&gidEnum, (void **)&gid, (void **)&op))
|
|
{
|
|
if ([[op object] isEqual:object] == YES)
|
|
{
|
|
found = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found == YES)
|
|
return op;
|
|
|
|
if (globalID == nil)
|
|
globalID = AUTORELEASE([[EOTemporaryGlobalID alloc] init]);
|
|
|
|
op = AUTORELEASE([[EODatabaseOperation alloc] initWithGlobalID:globalID
|
|
object:object
|
|
entity:entity]);
|
|
|
|
[op setDatabaseOperator:operator];
|
|
[op setDBSnapshot:EODatabaseContext_snapshotForGlobalIDWithImpPtr(self,NULL,globalID)];
|
|
|
|
newRow = [op newRow];
|
|
|
|
classProperties = [entity classProperties];
|
|
|
|
count = [classProperties count];
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[classProperties methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
attribute = GDL2_ObjectAtIndexWithImp(classProperties,oaiIMP,i);
|
|
if ([attribute isKindOfClass:GDL2_EOAttributeClass] == NO)
|
|
continue;
|
|
|
|
key = [attribute name];
|
|
|
|
if ([attribute isFlattened] == NO)
|
|
{
|
|
val = [object storedValueForKey:key];
|
|
|
|
if (val == nil)
|
|
val = GDL2_EONull;
|
|
|
|
[newRow setObject:val forKey:key];
|
|
}
|
|
}
|
|
};
|
|
|
|
NSMapInsert(_dbOperationsByGlobalID, globalID, op);
|
|
|
|
return op;
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
// private
|
|
- (NSArray*) _batchNewPrimaryKeysWithEntity:(EOEntity *) eoentity
|
|
count:(NSUInteger) i
|
|
{
|
|
NSArray * nsarray = nil;
|
|
|
|
NS_DURING {
|
|
nsarray = [[[self _obtainOpenChannel] adaptorChannel] primaryKeysForNewRowsWithEntity:eoentity
|
|
count:i];
|
|
} NS_HANDLER {
|
|
if ([self _delegateHandledDatabaseException:localException])
|
|
{
|
|
nsarray = [[[self _obtainOpenChannel] adaptorChannel] primaryKeysForNewRowsWithEntity:eoentity
|
|
count:i];
|
|
} else {
|
|
if ([[_database adaptor] isDroppedConnectionException:localException])
|
|
{
|
|
[_database handleDroppedConnection];
|
|
nsarray = [[[self _obtainOpenChannel] adaptorChannel] primaryKeysForNewRowsWithEntity:eoentity
|
|
count:i];
|
|
} else {
|
|
[localException raise];
|
|
}
|
|
}
|
|
} NS_ENDHANDLER;
|
|
|
|
return nsarray;
|
|
}
|
|
|
|
// Prepares to save changes. Obtains primary keys for any inserted objects
|
|
// in the EditingContext that are owned by this context.
|
|
- (void)prepareForSaveWithCoordinator: (EOObjectStoreCoordinator *)coordinator
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
NSArray *insertedObjects = nil;
|
|
NSUInteger count = 0, idx = 0;
|
|
NSMutableDictionary * rootDict = nil;
|
|
|
|
NSAssert(context, @"No editing context");
|
|
|
|
if (_flags.preparingForSave)
|
|
{
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"%s %@ is currently saving for %@ so it cannot prepare to save for %@.",
|
|
__PRETTY_FUNCTION__, self, _editingContext, context];
|
|
}
|
|
|
|
if (coordinator)
|
|
{
|
|
EOObjectStoreCoordinator * oldCoordinator = [self coordinator];
|
|
if (!oldCoordinator)
|
|
{
|
|
[self setCoordinator:coordinator];
|
|
} else {
|
|
if (oldCoordinator != coordinator)
|
|
{
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"%s %@ already has an EOObjectStoreCoordinator. "
|
|
@"The database context needs to be removed from the cooperating object stores "
|
|
@"before it can be assigned to a different coordinator.",
|
|
__PRETTY_FUNCTION__, self];
|
|
}
|
|
}
|
|
}
|
|
|
|
_flags.willPrepareForSave = NO;
|
|
_flags.preparingForSave = YES;
|
|
_editingContext=context;//RETAIN ?
|
|
|
|
if ([_missingObjectGIDs count] > 0)
|
|
{
|
|
NSArray * updatedObjects = [_editingContext updatedObjects];
|
|
NSUInteger idx = [updatedObjects count];
|
|
for (; idx > 0; idx--)
|
|
{
|
|
EOGlobalID * globalid = [_editingContext globalIDForObject:[updatedObjects objectAtIndex:idx-1]];
|
|
if ([_missingObjectGIDs member:globalid] != nil)
|
|
{
|
|
[NSException raise:NSObjectNotAvailableException
|
|
format:@"%s Cannot save the object with globalID %@. "
|
|
@"The row referenced by this globalID was missing from the database at the time a fetch was attempted. "
|
|
@"Either it was removed from the database after this application got a pointer to it, or there is a "
|
|
@"referential integrity problem with your database. To be notified when fetches fail, implement a "
|
|
@"delegate on EODatabaseContext that responds to"
|
|
@"databaseContext:failedToFetchObject:globalID:.",
|
|
__PRETTY_FUNCTION__, globalid];
|
|
}
|
|
}
|
|
}
|
|
|
|
// First, create dbOperation map if there's none
|
|
if (!_dbOperationsByGlobalID)
|
|
_dbOperationsByGlobalID = NSCreateMapTable(NSObjectMapKeyCallBacks,
|
|
NSObjectMapValueCallBacks,
|
|
32);
|
|
|
|
// Next, build list of Entity which need PK generator
|
|
[self _buildPrimaryKeyGeneratorListForEditingContext: context];
|
|
|
|
// Now get newly inserted objects
|
|
// For each object, we will recordInsertForObject: and relay PK if it is !nil
|
|
insertedObjects = [context insertedObjects];
|
|
|
|
ASSIGN(_checkPropagatedPKs, [NSMutableArray array]);
|
|
rootDict = [NSMutableDictionary dictionary];
|
|
|
|
count = [insertedObjects count];
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[insertedObjects methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (idx = 0; idx< count; idx++)
|
|
{
|
|
EOCustomObject * object = GDL2_ObjectAtIndexWithImp(insertedObjects,oaiIMP,idx);
|
|
EOEntity * entity = [_database entityForObject:object];
|
|
|
|
if (!entity)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
[self recordInsertForObject: object];
|
|
|
|
if ([self _shouldGeneratePrimaryKeyForEntityName:[entity name]])
|
|
{
|
|
NSDictionary * objectPK = nil;
|
|
|
|
objectPK = [self _primaryKeyForObject: object
|
|
raiseException: NO];
|
|
if (!objectPK)
|
|
{
|
|
NSString * rootName = [[entity rootParent] name];
|
|
NSMutableArray * rootPKArray = [rootDict objectForKey:rootName];
|
|
if (!rootPKArray)
|
|
{
|
|
rootPKArray = [NSMutableArray array];
|
|
[rootDict setObject:rootPKArray
|
|
forKey:rootName];
|
|
}
|
|
[rootPKArray addObject:object];
|
|
} else {
|
|
[[[self databaseOperationForObject: object] newRow] addEntriesFromDictionary:objectPK];
|
|
|
|
[self relayPrimaryKey: objectPK
|
|
object: object
|
|
entity: entity];
|
|
}
|
|
} else {
|
|
[_checkPropagatedPKs addObject:object];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ([rootDict count]) {
|
|
NSEnumerator * keyEnumerator = [rootDict keyEnumerator];
|
|
NSString * key = nil;
|
|
|
|
while ((key = [keyEnumerator nextObject]))
|
|
{
|
|
NSMutableArray * rootPKArray = [rootDict objectForKey:key];
|
|
NSUInteger keyCount = [rootPKArray count];
|
|
NSUInteger i = 0;
|
|
NSArray * newPKs = [self _batchNewPrimaryKeysWithEntity:[_database entityNamed:key]
|
|
count:keyCount];
|
|
|
|
if ((!newPKs) || ([newPKs count] != keyCount))
|
|
{
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"Adaptor %@ "
|
|
@"failed to provide new primary keys for entity '%@'.",
|
|
[_database adaptor], key];
|
|
}
|
|
|
|
for (i=0; i < keyCount; i++) {
|
|
NSDictionary * newPKDict = [newPKs objectAtIndex:i];
|
|
EOCustomObject * eo = [rootPKArray objectAtIndex:i];
|
|
|
|
[[[self databaseOperationForObject: eo] newRow] addEntriesFromDictionary:newPKDict];
|
|
|
|
[self relayPrimaryKey: newPKDict
|
|
object: eo
|
|
entity: [_database entityForObject:eo]];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// PRIVATE
|
|
|
|
- (void) _patchUpPK:(EODatabaseOperation *) op
|
|
{
|
|
if (!op)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (([op databaseOperator] == EODatabaseInsertOperator))
|
|
{
|
|
NSMutableDictionary * newRowDict = [op newRow];
|
|
EOEntity * entity = [op entity];
|
|
EOCustomObject * eo = [op object];
|
|
NSArray * pkAttributes = [entity primaryKeyAttributeNames];
|
|
BOOL isEONull = NO;
|
|
NSUInteger i = 0;
|
|
NSUInteger pkCount = [pkAttributes count];
|
|
|
|
|
|
for (; i < pkCount; i++) {
|
|
if (([newRowDict objectForKey:[pkAttributes objectAtIndex:i]] == GDL2_EONull))
|
|
{
|
|
isEONull = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isEONull)
|
|
{
|
|
NSArray * relationships = [entity relationships];
|
|
NSUInteger relCount = [relationships count];
|
|
|
|
for (i = 0; i < relCount; i++)
|
|
{
|
|
EORelationship * relationship = [relationships objectAtIndex:i];
|
|
EORelationship * inverseRelationship = [relationship inverseRelationship];
|
|
EOCustomObject * relEO = nil;
|
|
|
|
if ((!inverseRelationship) || (![inverseRelationship propagatesPrimaryKey]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
relEO = [eo valueForKey:[relationship name]];
|
|
|
|
if (relEO)
|
|
{
|
|
[self relayAttributesInRelationship:inverseRelationship
|
|
sourceObject:relEO
|
|
destinationObject:eo];
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)recordChangesInEditingContext
|
|
{
|
|
//_EOAssertSafeMultiThreadedAccess
|
|
|
|
[self _assertValidStateWithSelector:
|
|
@selector(recordChangesInEditingContext)];
|
|
|
|
|
|
NSArray * insertedObjects = [_editingContext insertedObjects];
|
|
NSArray * deletedObjects = [_editingContext deletedObjects];
|
|
NSArray * newObjects = nil;
|
|
|
|
NSUInteger i = 0;
|
|
|
|
for (i = [deletedObjects count]; i > 0; i--)
|
|
{
|
|
EOCustomObject * eo = [deletedObjects objectAtIndex:i - 1];
|
|
EOEntity * eoentity = [_database entityForObject:eo];
|
|
NSArray * relationships = nil;
|
|
NSUInteger idx = 0;
|
|
|
|
if (!eoentity)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
relationships = [eoentity relationships];
|
|
|
|
for (idx = [relationships count]; idx > 0; idx--)
|
|
{
|
|
NSDictionary * currentCSnapDict = nil;
|
|
EORelationship * relationship = [relationships objectAtIndex:idx-1];
|
|
|
|
if (![relationship isToManyToOne])
|
|
{
|
|
continue;
|
|
}
|
|
currentCSnapDict = [self _currentCommittedSnapshotForObject:eo];
|
|
|
|
if (currentCSnapDict)
|
|
{
|
|
NSArray * destinationObjects = [currentCSnapDict objectForKey:[relationship name]];
|
|
if (((id)destinationObjects == GDL2_EONull)) {
|
|
destinationObjects = nil;
|
|
}
|
|
[self nullifyAttributesInRelationship:relationship
|
|
sourceObject:eo
|
|
destinationObjects:destinationObjects];
|
|
|
|
}
|
|
}
|
|
|
|
[self recordDeleteForObject:eo];
|
|
}
|
|
|
|
newObjects = [[_editingContext updatedObjects] arrayByAddingObjectsFromArray:insertedObjects];
|
|
for (i = [newObjects count]; i > 0; i--)
|
|
{
|
|
EOCustomObject * eo = [newObjects objectAtIndex:i-1];
|
|
EOEntity * eoentity = [_database entityForObject:eo];
|
|
NSDictionary * updatedEOSnap = nil;
|
|
NSDictionary * currentCSnapDict = nil;
|
|
|
|
NSArray * relationships = nil;
|
|
NSDictionary * dbSnapshotDict = nil;
|
|
NSUInteger relCount = 0;
|
|
NSUInteger idx = 0;
|
|
|
|
if (!eoentity)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
[self recordUpdateForObject:eo
|
|
changes:nil];
|
|
|
|
updatedEOSnap = [eo snapshot];
|
|
|
|
if (!updatedEOSnap)
|
|
{
|
|
updatedEOSnap = [NSDictionary dictionary];
|
|
}
|
|
|
|
currentCSnapDict = [self _currentCommittedSnapshotForObject:eo];
|
|
|
|
if (!currentCSnapDict)
|
|
{
|
|
currentCSnapDict = [NSDictionary dictionary];
|
|
}
|
|
if ([self _shouldGeneratePrimaryKeyForEntityName:[eoentity name]])
|
|
{
|
|
NSDictionary * primaryKeyDict = [self _primaryKeyForObject:eo];
|
|
if (primaryKeyDict)
|
|
{
|
|
[[[self databaseOperationForObject:eo] newRow] addEntriesFromDictionary:primaryKeyDict];
|
|
}
|
|
}
|
|
|
|
relationships = [eoentity relationships];
|
|
relCount = [relationships count];
|
|
|
|
for (idx = 0; idx < relCount; idx++)
|
|
{
|
|
EORelationship * relationship = nil;
|
|
EORelationship * substRelationship = nil;
|
|
NSArray * classProperties = nil;
|
|
id updatedSnapValue = nil;
|
|
id currentSnapValue = nil;
|
|
|
|
if (!dbSnapshotDict)
|
|
{
|
|
dbSnapshotDict = [[self databaseOperationForObject:eo] dbSnapshot];
|
|
}
|
|
|
|
relationship = [relationships objectAtIndex:idx];
|
|
substRelationship = [relationship _substitutionRelationshipForRow:dbSnapshotDict];
|
|
|
|
if (!substRelationship)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
classProperties = [eoentity classProperties];
|
|
|
|
if (([classProperties indexOfObject:substRelationship] == NSNotFound))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
updatedSnapValue = [updatedEOSnap objectForKey:[substRelationship name]];
|
|
|
|
if (updatedSnapValue == GDL2_EONull)
|
|
{
|
|
updatedSnapValue = nil;
|
|
}
|
|
|
|
currentSnapValue = [currentCSnapDict objectForKey:[substRelationship name]];
|
|
|
|
if (currentSnapValue == GDL2_EONull)
|
|
{
|
|
currentSnapValue = nil;
|
|
}
|
|
|
|
if ([substRelationship isToMany])
|
|
{
|
|
NSArray * updatedSnapValues = (NSArray*) updatedSnapValue;
|
|
NSArray * currentSnapValues = (NSArray*) currentSnapValue;
|
|
NSMutableArray * snapsToUse = nil;
|
|
|
|
if (((updatedSnapValues == currentSnapValues)) ||
|
|
([currentSnapValues isEqualToArray:updatedSnapValues])) // TODO: check if isEqual is ok too.
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!updatedSnapValues) {
|
|
snapsToUse = (NSMutableArray*) currentSnapValues;
|
|
} else {
|
|
if (currentSnapValues) {
|
|
snapsToUse = [NSMutableArray arrayWithArray:currentSnapValues];
|
|
[snapsToUse removeObjectsInArray:updatedSnapValues];
|
|
}
|
|
}
|
|
|
|
if ((_useToManyCaching && (snapsToUse)) && ([snapsToUse count] > 0))
|
|
{
|
|
EODatabaseOperation * updatedEoDbOp = [self databaseOperationForObject:eo];
|
|
NSUInteger valCount = [updatedSnapValues count];
|
|
NSMutableArray * globalids = [NSMutableArray arrayWithCapacity:valCount];
|
|
NSUInteger x = 0;
|
|
|
|
for (; x < valCount; x++) {
|
|
EOCustomObject * updatedEo = [updatedSnapValues objectAtIndex:x];
|
|
EOGlobalID * globalid = [_editingContext globalIDForObject:updatedEo];
|
|
|
|
if ((!globalid) || ([globalid isTemporary]))
|
|
{
|
|
globalids = nil;
|
|
break;
|
|
}
|
|
[globalids addObject:globalid];
|
|
}
|
|
|
|
[updatedEoDbOp recordToManySnapshot:globalids
|
|
relationshipName:[relationship name]];
|
|
}
|
|
[self nullifyAttributesInRelationship:substRelationship
|
|
sourceObject:eo
|
|
destinationObjects:snapsToUse];
|
|
continue;
|
|
}
|
|
if (updatedSnapValue != currentSnapValue)
|
|
{
|
|
[self nullifyAttributesInRelationship:substRelationship
|
|
sourceObject:eo
|
|
destinationObjects:currentSnapValue];
|
|
}
|
|
}
|
|
|
|
} // for updated objs
|
|
|
|
for (i = [newObjects count]; i > 0; i--)
|
|
{
|
|
EOCustomObject * updatedEO = [newObjects objectAtIndex:i-1];
|
|
EOEntity * updatedEntity = [_database entityForObject:updatedEO];
|
|
NSDictionary * updateSnapDict = nil;
|
|
NSDictionary * comSnapUpdatedEO = nil;
|
|
NSArray * updatedEntityRelationships = nil;
|
|
NSDictionary * updatedEntitySnapshot = nil;
|
|
NSUInteger updatedEntityRelCount = 0;
|
|
NSUInteger relIdx = 0;
|
|
|
|
if (updatedEntity == nil)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
[self recordUpdateForObject:updatedEO
|
|
changes:nil];
|
|
|
|
updateSnapDict = [updatedEO snapshot];
|
|
|
|
if (!updateSnapDict)
|
|
{
|
|
updateSnapDict = [NSDictionary dictionary];
|
|
}
|
|
|
|
comSnapUpdatedEO = [self _currentCommittedSnapshotForObject:updatedEO];
|
|
|
|
if (!comSnapUpdatedEO)
|
|
{
|
|
comSnapUpdatedEO = [NSDictionary dictionary];
|
|
}
|
|
|
|
updatedEntityRelationships = [updatedEntity relationships];
|
|
|
|
updatedEntityRelCount = [updatedEntityRelationships count];
|
|
|
|
for (relIdx = 0; relIdx < updatedEntityRelCount; relIdx++)
|
|
{
|
|
EORelationship * updatedEntityRel = nil;
|
|
EORelationship * substitutionRel = nil;
|
|
id updatedSnapValue = nil;
|
|
id currentSnapValue = nil;
|
|
|
|
if (!updatedEntitySnapshot)
|
|
{
|
|
updatedEntitySnapshot = [[self databaseOperationForObject:updatedEO] dbSnapshot];
|
|
}
|
|
|
|
updatedEntityRel = [updatedEntityRelationships objectAtIndex:relIdx];
|
|
substitutionRel = [updatedEntityRel _substitutionRelationshipForRow:updatedEntitySnapshot];
|
|
|
|
if ((!substitutionRel) || ([[updatedEntity classProperties] indexOfObject:substitutionRel] == NSNotFound))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
updatedSnapValue = [updateSnapDict objectForKey:[substitutionRel name]];
|
|
|
|
if (updatedSnapValue == GDL2_EONull)
|
|
{
|
|
updatedSnapValue = nil;
|
|
}
|
|
|
|
currentSnapValue = [comSnapUpdatedEO objectForKey:[substitutionRel name]];
|
|
|
|
if (currentSnapValue == GDL2_EONull)
|
|
{
|
|
currentSnapValue = nil;
|
|
}
|
|
|
|
if ([substitutionRel isToMany])
|
|
{
|
|
NSArray * updatedSnapValues = (NSArray*) updatedSnapValue;
|
|
NSArray * currentSnapValues = (NSArray*) currentSnapValue;
|
|
NSMutableArray * snaps = nil;
|
|
|
|
if ((updatedSnapValues == currentSnapValues) ||
|
|
[currentSnapValues isEqualToArray:updatedSnapValues])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!currentSnapValues) {
|
|
snaps = (NSMutableArray*) updatedSnapValues;
|
|
} else {
|
|
snaps = [NSMutableArray arrayWithArray:updatedSnapValues];
|
|
[snaps removeObjectsInArray:currentSnapValues];
|
|
}
|
|
|
|
if ((_useToManyCaching && (snaps)) && ([snaps count] > 0))
|
|
{
|
|
EODatabaseOperation * updatedEoDbOp = [self databaseOperationForObject:updatedEO];
|
|
NSUInteger valCount = [updatedSnapValues count];
|
|
NSUInteger y = 0;
|
|
NSMutableArray * gidArray = [NSMutableArray arrayWithCapacity:valCount];
|
|
|
|
for (; y < valCount; y++)
|
|
{
|
|
EOGlobalID * gid = [_editingContext globalIDForObject:[updatedSnapValues objectAtIndex:y]];
|
|
|
|
if ((!gid) || ([gid isTemporary]))
|
|
{
|
|
gidArray = nil;
|
|
break;
|
|
}
|
|
[gidArray addObject:gid];
|
|
}
|
|
|
|
[updatedEoDbOp recordToManySnapshot:gidArray
|
|
relationshipName:[updatedEntityRel name]];
|
|
|
|
}
|
|
[self relayAttributesInRelationship:substitutionRel
|
|
sourceObject:updatedEO
|
|
destinationObjects:snaps];
|
|
continue;
|
|
}
|
|
if (updatedSnapValue != currentSnapValue)
|
|
{
|
|
[self relayAttributesInRelationship:substitutionRel
|
|
sourceObject:updatedEO
|
|
destinationObject:updatedSnapValue];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (_checkPropagatedPKs)
|
|
{
|
|
NSUInteger pkCount = [_checkPropagatedPKs count];
|
|
|
|
for (i = 0; i < pkCount; i++)
|
|
{
|
|
EOCustomObject * eo = [_checkPropagatedPKs objectAtIndex:i];
|
|
[self _patchUpPK:[self databaseOperationForObject:eo]];
|
|
}
|
|
|
|
}
|
|
_checkPropagatedPKs = nil;
|
|
}
|
|
|
|
|
|
/** Contructs a list of EODatabaseOperations for all changes in the EOEditingContext
|
|
that are owned by this context. Forward any relationship changes discovered
|
|
but not owned by this context to the coordinator.
|
|
**/
|
|
- (void)recordUpdateForObject: (id)object
|
|
changes: (NSDictionary *)changes
|
|
{
|
|
EODatabaseOperation *dbOpe = nil;
|
|
|
|
NSAssert(object, @"No object");
|
|
|
|
[self _assertValidStateWithSelector:
|
|
@selector(recordUpdateForObject:changes:)];
|
|
|
|
dbOpe = [self databaseOperationForObject: object];
|
|
|
|
if (dbOpe) {
|
|
[dbOpe setDatabaseOperator:EODatabaseUpdateOperator];
|
|
if ((changes) && ([changes count]))
|
|
{
|
|
[[dbOpe newRow] addEntriesFromDictionary: changes];
|
|
}
|
|
} else {
|
|
[[self coordinator] forwardUpdateForObject:object
|
|
changes:changes];
|
|
}
|
|
}
|
|
|
|
-(void)recordInsertForObject: (id)object
|
|
{
|
|
NSDictionary *snapshot = nil;
|
|
EODatabaseOperation *dbOpe = nil;
|
|
|
|
dbOpe = [self databaseOperationForObject: object];
|
|
[dbOpe setDatabaseOperator: EODatabaseInsertOperator];
|
|
|
|
snapshot = [dbOpe dbSnapshot];
|
|
|
|
if ([snapshot count] != 0)
|
|
{
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"%s found a snapshot for EO with Global ID: %@ that has been inserted into %@."
|
|
@"Cannot insert an object that is already in the database",
|
|
__PRETTY_FUNCTION__, [dbOpe globalID], _editingContext];
|
|
}
|
|
|
|
}
|
|
|
|
- (void) recordDeleteForObject: (id)object
|
|
{
|
|
NSDictionary *snapshot = nil;
|
|
EODatabaseOperation *dbOpe = nil;
|
|
|
|
dbOpe = [self databaseOperationForObject: object];
|
|
|
|
[dbOpe setDatabaseOperator: EODatabaseDeleteOperator];
|
|
|
|
snapshot = [dbOpe dbSnapshot];
|
|
|
|
if (([snapshot count] == 0))
|
|
{
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"%s failed to find a snapshot for EO with Global ID: %@ that has been deleted from %@."
|
|
@"Cannot delete an object that has not been fetched from the database",
|
|
__PRETTY_FUNCTION__, [dbOpe globalID], _editingContext];
|
|
}
|
|
}
|
|
|
|
/** Constructs EOAdaptorOperations for all EODatabaseOperations constructed in
|
|
during recordChangesInEditingContext and recordUpdateForObject:changes:.
|
|
Performs the EOAdaptorOperations on an available adaptor channel.
|
|
If the save is OK, updates the snapshots in the EODatabaseContext to reflect
|
|
the new state of the server.
|
|
Raises an exception is the adaptor is unable to perform the operations.
|
|
**/
|
|
- (void)performChanges
|
|
{
|
|
NSMapEnumerator dbOpeEnum;
|
|
EOGlobalID *gid = nil;
|
|
EODatabaseOperation *dbOpe = nil;
|
|
NSArray *orderedAdaptorOperations = nil;
|
|
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"self=%p preparingForSave=%d beganTransaction=%d",
|
|
self,
|
|
(int)_flags.preparingForSave,
|
|
(int)_flags.beganTransaction);
|
|
|
|
[self _assertValidStateWithSelector: @selector(performChanges)];
|
|
|
|
dbOpeEnum = NSEnumerateMapTable(_dbOperationsByGlobalID);
|
|
|
|
while (NSNextMapEnumeratorPair(&dbOpeEnum, (void **)&gid, (void **)&dbOpe))
|
|
{
|
|
|
|
|
|
//REVOIR
|
|
if ([dbOpe databaseOperator] == EODatabaseNothingOperator)
|
|
{
|
|
NSDebugMLLog(@"EODatabaseContext", @"Db Ope %@ for Nothing !!!",
|
|
dbOpe);
|
|
}
|
|
else
|
|
{
|
|
[self _verifyNoChangesToReadonlyEntity: dbOpe];
|
|
//MIRKO snapshot = [op dbSnapshot];
|
|
[self createAdaptorOperationsForDatabaseOperation: dbOpe];
|
|
}
|
|
}
|
|
// avoid leaks! -- dw
|
|
NSEndMapTableEnumeration(&dbOpeEnum);
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"orderedAdaptorOperations A=%@",
|
|
orderedAdaptorOperations);
|
|
|
|
orderedAdaptorOperations = [self orderAdaptorOperations];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"orderedAdaptorOperations B=%@",
|
|
orderedAdaptorOperations);
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"self=%p preparingForSave=%d beganTransaction=%d",
|
|
self,
|
|
(int)_flags.preparingForSave,
|
|
(int)_flags.beganTransaction);
|
|
|
|
if ([orderedAdaptorOperations count] > 0)
|
|
{
|
|
EOAdaptorChannel *adaptorChannel = nil;
|
|
EODatabaseChannel *dbChannel = [self _obtainOpenChannel];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"self=%p preparingForSave=%d beganTransaction=%d",
|
|
self,
|
|
(int)_flags.preparingForSave,
|
|
(int)_flags.beganTransaction);
|
|
|
|
if (_flags.beganTransaction == NO)//MIRKO
|
|
{
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"self=%p [_adaptorContext transactionNestingLevel]=%d",
|
|
self,
|
|
(int)[_adaptorContext transactionNestingLevel]);
|
|
|
|
if ([_adaptorContext transactionNestingLevel] == 0) //??
|
|
[_adaptorContext beginTransaction];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"BEGAN TRANSACTION FLAG==>YES");
|
|
|
|
_flags.beganTransaction = YES;
|
|
}
|
|
|
|
adaptorChannel = [dbChannel adaptorChannel];
|
|
|
|
if (_delegateRespondsTo.willPerformAdaptorOperations == YES)
|
|
orderedAdaptorOperations = [_delegate databaseContext: self
|
|
willPerformAdaptorOperations:
|
|
orderedAdaptorOperations
|
|
adaptorChannel: adaptorChannel];
|
|
NS_DURING
|
|
{
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"performAdaptorOperations:");
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"self=%p preparingForSave=%d beganTransaction=%d",
|
|
self,
|
|
(int)_flags.preparingForSave,
|
|
(int)_flags.beganTransaction);
|
|
|
|
[adaptorChannel performAdaptorOperations: orderedAdaptorOperations];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"self=%p preparingForSave=%d beganTransaction=%d",
|
|
self,
|
|
(int)_flags.preparingForSave,
|
|
(int)_flags.beganTransaction);
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"after performAdaptorOperations:");
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"Exception in performAdaptorOperations:%@",
|
|
localException);
|
|
[localException raise];
|
|
//MIRKO
|
|
//TODO
|
|
/*
|
|
NSException *exp;
|
|
NSMutableDictionary *userInfo;
|
|
EOAdaptorOperation *adaptorOp;
|
|
|
|
userInfo = [NSMutableDictionary dictionaryWithCapacity:10];
|
|
[userInfo addEntriesFromDictionary:[localException userInfo]];
|
|
[userInfo setObject:self forKey:EODatabaseContextKey];
|
|
[userInfo setObject:dbOps
|
|
forKey:EODatabaseOperationsKey];
|
|
|
|
adaptorOp = [userInfo objectForKey:EOFailedAdaptorOperationKey];
|
|
|
|
dbEnum = [dbOps objectEnumerator];
|
|
while ((op = [dbEnum nextObject]))
|
|
if ([[op adaptorOperations] containsObject:adaptorOp] == YES)
|
|
{
|
|
[userInfo setObject:op
|
|
forKey:EOFailedDatabaseOperationKey];
|
|
break;
|
|
}
|
|
|
|
exp = [NSException exceptionWithName:EOGeneralDatabaseException
|
|
reason:[NSString stringWithFormat:
|
|
@"%@ -- %@ 0x%x: failed with exception name:%@ reason:\"%@\"",
|
|
NSStringFromSelector(_cmd),
|
|
NSStringFromClass([self class]),
|
|
self, [localException name],
|
|
[localException reason]]
|
|
userInfo:userInfo];
|
|
|
|
[exp raise];
|
|
*/
|
|
}
|
|
NS_ENDHANDLER;
|
|
|
|
//This is not done by mirko:
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"self=%p preparingForSave=%d beganTransaction=%d",
|
|
self,
|
|
(int)_flags.preparingForSave,
|
|
(int)_flags.beganTransaction);
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"self=%p _uniqueStack %p=%@",
|
|
self, _uniqueStack, _uniqueStack);
|
|
|
|
dbOpeEnum = NSEnumerateMapTable(_dbOperationsByGlobalID);
|
|
|
|
while (NSNextMapEnumeratorPair(&dbOpeEnum, (void **)&gid,
|
|
(void **)&dbOpe))
|
|
{
|
|
EODatabaseOperator databaseOperator = EODatabaseNothingOperator;
|
|
|
|
//call dbOpe adaptorOperations ?
|
|
if ([dbOpe databaseOperator] == EODatabaseNothingOperator)
|
|
{
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"Db Ope %@ for Nothing !!!", dbOpe);
|
|
}
|
|
else
|
|
{
|
|
EOEntity *entity = nil;
|
|
NSArray *dbSnapshotKeys = nil;
|
|
NSMutableDictionary *newRow = nil;
|
|
NSDictionary *values = nil;
|
|
id object = nil;
|
|
NSArray *adaptorOpe = nil;
|
|
|
|
|
|
|
|
object = [dbOpe object];
|
|
adaptorOpe = [dbOpe adaptorOperations];
|
|
databaseOperator = [dbOpe databaseOperator];
|
|
entity = [dbOpe entity];
|
|
dbSnapshotKeys = [entity dbSnapshotKeys];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"dbSnapshotKeys=%@",
|
|
dbSnapshotKeys);
|
|
|
|
newRow = [dbOpe newRow];
|
|
|
|
|
|
values = [newRow dictionaryWithValuesForKeys: dbSnapshotKeys];
|
|
//if update: forgetSnapshotForGlobalID:
|
|
|
|
[self recordSnapshot: values
|
|
forGlobalID: gid];
|
|
|
|
if (databaseOperator == EODatabaseUpdateOperator) //OK for update //Do it forInsert too //TODO
|
|
{
|
|
NSDictionary *toManySnapshots = [dbOpe toManySnapshots];
|
|
|
|
if (toManySnapshots)
|
|
{
|
|
NSDebugMLog(@"toManySnapshots=%@", toManySnapshots);
|
|
NSEmitTODO();
|
|
|
|
//TODONOW [self notImplemented: _cmd]; //TODO
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// avoid leaks! -- dw
|
|
NSEndMapTableEnumeration(&dbOpeEnum);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (void) commitChanges
|
|
{
|
|
NSMapEnumerator dbOpeEnum;
|
|
EOGlobalID *gid = nil;
|
|
EODatabaseOperation *dbOpe = nil;
|
|
NSMutableArray *deletedObjects = [NSMutableArray array];
|
|
NSMutableArray *insertedObjects = [NSMutableArray array];
|
|
NSMutableArray *updatedObjects = [NSMutableArray array];
|
|
NSMutableDictionary *gidChangedUserInfo = nil;
|
|
NSMutableDictionary *gidChangedUserInfo2 = nil;
|
|
|
|
[self _assertValidStateWithSelector: @selector(commitChanges)];
|
|
|
|
// before we send new changes to the database,
|
|
// make sure we have commited everything
|
|
|
|
NS_DURING {
|
|
if ([_adaptorContext hasOpenTransaction]) {
|
|
[_adaptorContext commitTransaction];
|
|
_flags.beganTransaction = NO;
|
|
}
|
|
} NS_HANDLER {
|
|
[self rollbackChanges];
|
|
[localException raise];
|
|
|
|
} NS_ENDHANDLER;
|
|
// useToManyCaching ?
|
|
|
|
[EOObserverCenter suppressObserverNotification];
|
|
dbOpeEnum = NSEnumerateMapTable(_dbOperationsByGlobalID);
|
|
|
|
NS_DURING {
|
|
while (NSNextMapEnumeratorPair(&dbOpeEnum, (void **)&gid,
|
|
(void **)&dbOpe))
|
|
{
|
|
EODatabaseOperator databaseOperator = EODatabaseNothingOperator;
|
|
EOGlobalID *dbOpeGID = nil;
|
|
EOGlobalID *newGID = nil;
|
|
EOEntity *entity = nil;
|
|
NSDictionary *newRowValues = nil;
|
|
NSMutableDictionary *newRow = [dbOpe newRow];
|
|
|
|
databaseOperator = [dbOpe databaseOperator];
|
|
entity = [dbOpe entity];
|
|
|
|
|
|
if ([gid isTemporary] || ([[dbOpe primaryKeyDiffs] count] > 0))
|
|
{
|
|
newGID = [entity globalIDForRow: newRow
|
|
isFinal: YES];
|
|
if (!gidChangedUserInfo)
|
|
{
|
|
gidChangedUserInfo = [NSMutableDictionary dictionary];
|
|
}
|
|
[gidChangedUserInfo setObject: newGID
|
|
forKey: gid];
|
|
}
|
|
|
|
switch (databaseOperator)
|
|
{
|
|
case EODatabaseInsertOperator:
|
|
|
|
newRowValues = [newRow dictionaryWithValuesForKeys:[entity classPropertyAttributeNames]];
|
|
break;
|
|
|
|
case EODatabaseUpdateOperator:
|
|
newRowValues = [dbOpe rowDiffsForAttributes: [entity _classPropertyAttributes]];
|
|
// NSLog(@"%s %d _classPropertyAttributes %@",__PRETTY_FUNCTION__, __LINE__, [entity _classPropertyAttributes] );
|
|
// NSLog(@"%s %d newRowValues %@",__PRETTY_FUNCTION__, __LINE__, newRowValues );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
id object = [dbOpe object];
|
|
if (object)
|
|
{
|
|
if ((newRowValues) && ([newRowValues count] > 0))
|
|
{
|
|
[object takeStoredValuesFromDictionary:newRowValues];
|
|
}
|
|
if (databaseOperator == EODatabaseInsertOperator)
|
|
{
|
|
[_database incrementSnapshotCountForGlobalID:dbOpeGID];
|
|
}
|
|
}
|
|
} // while
|
|
} NS_HANDLER {
|
|
[EOObserverCenter enableObserverNotification];
|
|
NSEndMapTableEnumeration(&dbOpeEnum);
|
|
[localException raise];
|
|
} NS_ENDHANDLER;
|
|
|
|
[EOObserverCenter enableObserverNotification];
|
|
NSEndMapTableEnumeration(&dbOpeEnum);
|
|
|
|
if (gidChangedUserInfo)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName: EOGlobalIDChangedNotification
|
|
object: self
|
|
userInfo: gidChangedUserInfo];
|
|
}
|
|
|
|
dbOpeEnum = NSEnumerateMapTable(_dbOperationsByGlobalID);
|
|
gidChangedUserInfo2 = [NSMutableDictionary dictionary];
|
|
|
|
while (NSNextMapEnumeratorPair(&dbOpeEnum, (void **)&gid,
|
|
(void **)&dbOpe)) {
|
|
EOGlobalID *dbOpeGID = nil;
|
|
|
|
dbOpeGID = [dbOpe globalID];
|
|
|
|
switch ([dbOpe databaseOperator])
|
|
{
|
|
case EODatabaseInsertOperator:
|
|
{
|
|
id newObj = nil;
|
|
if (gidChangedUserInfo) {
|
|
newObj = [gidChangedUserInfo objectForKey:dbOpeGID];
|
|
}
|
|
|
|
[insertedObjects addObject: (newObj == nil ? dbOpeGID : newObj)];
|
|
}
|
|
break;
|
|
|
|
case EODatabaseDeleteOperator:
|
|
[deletedObjects addObject: dbOpeGID];
|
|
break;
|
|
|
|
case EODatabaseUpdateOperator: /* 2 */
|
|
[updatedObjects addObject: dbOpeGID];
|
|
break;
|
|
|
|
case EODatabaseNothingOperator:
|
|
break;
|
|
}
|
|
}
|
|
NSEndMapTableEnumeration(&dbOpeEnum);
|
|
|
|
[gidChangedUserInfo2 setObject:deletedObjects
|
|
forKey:EODeletedKey];
|
|
|
|
[gidChangedUserInfo2 setObject:insertedObjects
|
|
forKey:EOInsertedKey];
|
|
|
|
[gidChangedUserInfo2 setObject:updatedObjects
|
|
forKey:EOUpdatedKey];
|
|
|
|
[self _cleanUpAfterSave];
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName: @"EOObjectsChangedInStoreNotification"
|
|
object: _database
|
|
userInfo: gidChangedUserInfo2];
|
|
}
|
|
|
|
- (void)rollbackChanges
|
|
{ // TODO
|
|
//adaptorcontext transactionNestingLevel
|
|
//if 0 ? _cleanUpAfterSave
|
|
|
|
|
|
if (_flags.beganTransaction == YES)
|
|
{
|
|
[_adaptorContext rollbackTransaction];
|
|
|
|
_flags.beganTransaction = NO;
|
|
_flags.preparingForSave = NO;
|
|
|
|
if (_lockedObjects)
|
|
{
|
|
NSResetHashTable(_lockedObjects);
|
|
}
|
|
|
|
NSResetMapTable(_dbOperationsByGlobalID);
|
|
/* //TODO
|
|
[_snapshots removeAllObjects];
|
|
[_toManySnapshots removeAllObjects];
|
|
*/
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (NSDictionary *)valuesForKeys: (NSArray *)keys
|
|
object: (id)object
|
|
{
|
|
EOEntity *entity;
|
|
EODatabaseOperation *dbOpe;
|
|
NSDictionary *newRow;
|
|
NSDictionary *values = nil;
|
|
|
|
if (!_isNilOrEONull(object))
|
|
{
|
|
entity = [_database entityForObject: object];
|
|
|
|
NSAssert1(entity, @"No entity for object %@", object);
|
|
|
|
|
|
dbOpe = [self databaseOperationForObject: object];
|
|
newRow = [dbOpe newRow];
|
|
values = [newRow dictionaryWithValuesForKeys: keys];
|
|
}
|
|
else
|
|
{
|
|
values = [NSDictionary dictionary];
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
-(void)nullifyAttributesInRelationship: (EORelationship*)relationship
|
|
sourceObject: (id)sourceObject
|
|
destinationObject: (id)destinationObject
|
|
{
|
|
EODatabaseOperation *sourceDBOpe = nil;
|
|
|
|
|
|
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"destinationObject=%@",
|
|
destinationObject);
|
|
|
|
if (destinationObject)
|
|
{
|
|
//Get SourceObject database operation
|
|
sourceDBOpe = [self databaseOperationForObject: sourceObject]; //TODO: useIt
|
|
|
|
|
|
|
|
if ([relationship isToManyToOne])
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
else
|
|
{
|
|
// Key a dictionary of two array: destinationKeys and sourceKeys
|
|
NSDictionary *sourceToDestinationKeyMap =
|
|
[relationship _sourceToDestinationKeyMap]; //{destinationKeys = (customerCode); sourceKeys = (code); }
|
|
BOOL foreignKeyInDestination = [relationship foreignKeyInDestination];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"sourceToDestinationKeyMap=%@",
|
|
sourceToDestinationKeyMap);
|
|
NSDebugMLLog(@"EODatabaseContext", @"foreignKeyInDestination=%d",
|
|
foreignKeyInDestination);
|
|
|
|
if (foreignKeyInDestination)
|
|
{
|
|
NSArray *destinationKeys = [sourceToDestinationKeyMap
|
|
objectForKey: @"destinationKeys"];//(customerCode)
|
|
int i, destinationKeysCount = [destinationKeys count];
|
|
NSMutableDictionary *changes = [NSMutableDictionary dictionaryWithCapacity: destinationKeysCount];
|
|
|
|
if (destinationKeysCount>0)
|
|
{
|
|
IMP oaiIMP=[destinationKeys methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i = 0 ;i < destinationKeysCount; i++)
|
|
{
|
|
id destinationKey = GDL2_ObjectAtIndexWithImp(destinationKeys,oaiIMP,i);
|
|
|
|
[changes setObject: GDL2_EONull
|
|
forKey: destinationKey];
|
|
}
|
|
}
|
|
|
|
NSAssert1(destinationObject,
|
|
@"No destinationObject for call of recordUpdateForObject:changes: changes: %@",
|
|
changes);
|
|
|
|
[self recordUpdateForObject: destinationObject
|
|
changes: changes];
|
|
}
|
|
else
|
|
{
|
|
//Do nothing ?
|
|
NSEmitTODO();
|
|
//[self notImplemented: _cmd]; //TODO
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// private
|
|
|
|
-(NSDictionary*) _primaryKeyForIntermediateRowFromSourceObject:(EOCustomObject*) srcObject
|
|
relationship:(EORelationship *) relationship
|
|
destinationObject:(EOCustomObject *)destObject
|
|
{
|
|
NSMutableDictionary * pkDict = nil;
|
|
NSMutableDictionary * pkDict1 = nil;
|
|
NSDictionary * leftSideKeyDict = [relationship _leftSideKeyMap];
|
|
EODatabaseOperation * op = [self databaseOperationForObject:srcObject];
|
|
NSArray * nsarray = [leftSideKeyDict objectForKey:@"sourceKeys"];
|
|
|
|
pkDict = [NSMutableDictionary dictionaryWithDictionary:[op newRow]
|
|
keys:nsarray];
|
|
|
|
if ([pkDict containsAnyNullObject])
|
|
{
|
|
NSDictionary * dict = [[self _entityForObject:srcObject] primaryKeyForGlobalID:(EOKeyGlobalID*)[op globalID]];
|
|
|
|
pkDict = [NSMutableDictionary dictionaryWithDictionary:dict
|
|
keys:nsarray];
|
|
}
|
|
|
|
[pkDict translateFromKeys:nsarray
|
|
toKeys:(NSArray*) [leftSideKeyDict objectForKey:@"destinationKeys"]];
|
|
|
|
leftSideKeyDict = [relationship _rightSideKeyMap];
|
|
|
|
op = [self databaseOperationForObject:destObject];
|
|
nsarray = (NSArray*)[leftSideKeyDict objectForKey:@"destinationKeys"];
|
|
|
|
pkDict1 = [NSMutableDictionary dictionaryWithDictionary:[op newRow]
|
|
keys:nsarray];
|
|
|
|
if ([pkDict1 containsAnyNullObject])
|
|
{
|
|
NSDictionary * destKeyDict = [[self _entityForObject:destObject]
|
|
primaryKeyForGlobalID:(EOKeyGlobalID*)[op globalID]];
|
|
|
|
pkDict1 = [NSMutableDictionary dictionaryWithDictionary:destKeyDict
|
|
keys:nsarray];
|
|
}
|
|
[pkDict1 translateFromKeys:nsarray
|
|
toKeys:(NSArray*)[leftSideKeyDict objectForKey:@"sourceKeys"]];
|
|
|
|
[pkDict addEntriesFromDictionary:pkDict1];
|
|
|
|
return pkDict;
|
|
}
|
|
|
|
// private
|
|
|
|
- (EODatabaseOperation*) _databaseOperationForIntermediateRowFromSourceObject:(EOCustomObject*) srcObject
|
|
relationship:(EORelationship *) relationship
|
|
destinationObject:(EOCustomObject *)destObject
|
|
{
|
|
EOEntity * intermediateEntity = nil;
|
|
NSDictionary * pkDict = nil;
|
|
EOGlobalID * gid = nil;
|
|
EODatabaseOperation * op = nil;
|
|
|
|
pkDict = [self _primaryKeyForIntermediateRowFromSourceObject:srcObject
|
|
relationship:relationship
|
|
destinationObject:destObject];
|
|
|
|
intermediateEntity = [relationship intermediateEntity];
|
|
|
|
gid = [intermediateEntity globalIDForRow:pkDict];
|
|
|
|
if (!gid)
|
|
{
|
|
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"A valid global ID could not be obtained for entity named '"
|
|
@"%@' relationship named '%@' primary key dictionary %@.",
|
|
[intermediateEntity name], [relationship name], pkDict];
|
|
}
|
|
|
|
op = [self databaseOperationForGlobalID:gid];
|
|
|
|
if (!op)
|
|
{
|
|
op = [EODatabaseOperation databaseOperationWithGlobalID: gid
|
|
object: nil
|
|
entity: intermediateEntity];
|
|
|
|
[op setDBSnapshot:pkDict];
|
|
[op setNewRow:[NSMutableDictionary dictionaryWithDictionary:pkDict]];
|
|
|
|
[self recordDatabaseOperation:op];
|
|
}
|
|
return op;
|
|
}
|
|
|
|
// private
|
|
- (void) _recordDeleteForIntermediateRowFromSourceObject:(EOCustomObject*) sourceObject
|
|
relationship:(EORelationship*) relationship
|
|
destinationObject:(EOCustomObject*) destinationObject
|
|
{
|
|
EODatabaseOperation * op;
|
|
|
|
op = [self _databaseOperationForIntermediateRowFromSourceObject:sourceObject
|
|
relationship:relationship
|
|
destinationObject:destinationObject];
|
|
|
|
[op setDatabaseOperator:EODatabaseDeleteOperator];
|
|
}
|
|
|
|
- (void)nullifyAttributesInRelationship: (EORelationship*)relationship
|
|
sourceObject: (id)sourceObject
|
|
destinationObjects: (NSArray*)destinationObjects
|
|
{
|
|
EODatabaseOperation * dbOpe = nil;
|
|
|
|
if (!destinationObjects)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dbOpe = [self databaseOperationForObject:sourceObject];
|
|
|
|
if ([relationship isToManyToOne])
|
|
{
|
|
[self _recordDeleteForIntermediateRowFromSourceObject: sourceObject
|
|
relationship:relationship
|
|
destinationObject:(id)destinationObjects];
|
|
|
|
} else {
|
|
NSDictionary * mappingDict = [relationship _sourceToDestinationKeyMap];
|
|
if ([relationship foreignKeyInDestination])
|
|
{
|
|
NSArray * destinationKeys = [mappingDict objectForKey:@"destinationKeys"];
|
|
|
|
[self recordUpdateForObject:destinationObjects
|
|
changes:[NSDictionary dictionaryWithNullValuesForKeys:destinationKeys]];
|
|
} else
|
|
{
|
|
NSArray * sourceKeys = [mappingDict objectForKey:@"sourceKeys"];
|
|
|
|
[[dbOpe newRow] addEntriesFromDictionary:
|
|
[NSDictionary dictionaryWithNullValuesForKeys:sourceKeys]];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
- (void)relayAttributesInRelationship: (EORelationship*)relationship
|
|
sourceObject: (id)sourceObject
|
|
destinationObjects: (NSArray*)destinationObjects
|
|
{
|
|
int destinationObjectsCount = 0;
|
|
|
|
destinationObjectsCount = [destinationObjects count];
|
|
|
|
if (destinationObjectsCount > 0)
|
|
{
|
|
int i;
|
|
IMP oaiIMP=[destinationObjects methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i = 0; i < destinationObjectsCount; i++)
|
|
{
|
|
id object = GDL2_ObjectAtIndexWithImp(destinationObjects,oaiIMP,i);
|
|
|
|
[self relayAttributesInRelationship: (EORelationship*)relationship
|
|
sourceObject: (id)sourceObject
|
|
destinationObject: object];
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (NSDictionary*)relayAttributesInRelationship: (EORelationship*)relationship
|
|
sourceObject: (id)sourceObject
|
|
destinationObject: (id)destinationObject
|
|
{
|
|
//OK
|
|
NSMutableDictionary *relayedValues = nil;
|
|
EODatabaseOperation *sourceDBOpe = nil;
|
|
|
|
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"sourceObject %p=%@ (class=%@)",
|
|
sourceObject, sourceObject, [sourceObject class]);
|
|
NSDebugMLLog(@"EODatabaseContext", @"destinationObject %p=%@ (class=%@)",
|
|
destinationObject, destinationObject,
|
|
[destinationObject class]);
|
|
|
|
//Get SourceObject database operation
|
|
sourceDBOpe = [self databaseOperationForObject: sourceObject];
|
|
|
|
|
|
|
|
if ([sourceDBOpe databaseOperator] == EODatabaseNothingOperator)
|
|
{
|
|
NSDebugMLLog(@"EODatabaseContext", @"Db Ope %@ for Nothing !!!",
|
|
sourceDBOpe);
|
|
}
|
|
|
|
if ([relationship isToManyToOne])
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
else
|
|
{
|
|
// Key a dictionary of two array: destinationKeys and sourceKeys
|
|
NSDictionary *sourceToDestinationKeyMap = [relationship _sourceToDestinationKeyMap];//{destinationKeys = (customerCode); sourceKeys = (code); }
|
|
NSArray *destinationKeys = [sourceToDestinationKeyMap
|
|
objectForKey: @"destinationKeys"];//(customerCode)
|
|
NSArray *sourceKeys = [sourceToDestinationKeyMap
|
|
objectForKey: @"sourceKeys"];//(code)
|
|
NSMutableDictionary *sourceNewRow = [sourceDBOpe newRow];//OK in foreignKeyInDestination
|
|
BOOL foreignKeyInDestination = [relationship foreignKeyInDestination];
|
|
int i, count;
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"sourceToDestinationKeyMap=%@",
|
|
sourceToDestinationKeyMap);
|
|
NSDebugMLLog(@"EODatabaseContext", @"destinationKeys=%@",
|
|
destinationKeys);
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"foreignKeyInDestination=%s",
|
|
(foreignKeyInDestination ? "YES" : "NO"));
|
|
|
|
NSAssert([destinationKeys count] == [sourceKeys count],
|
|
@"destination keys count!=source keys count");
|
|
|
|
if (foreignKeyInDestination || [relationship propagatesPrimaryKey])
|
|
{
|
|
IMP srcObjectAIndexIMP=[sourceKeys methodForSelector: @selector(objectAtIndex:)];
|
|
IMP dstObjectAIndexIMP=[destinationKeys methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
relayedValues = AUTORELEASE([[sourceNewRow dictionaryWithValuesForKeys: sourceKeys]
|
|
mutableCopy]);// {code = 0; }
|
|
NSDebugMLLog(@"EODatabaseContext", @"relayedValues=%@",
|
|
relayedValues);
|
|
|
|
count = [relayedValues count];
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
NSString *sourceKey = GDL2_ObjectAtIndexWithImp(sourceKeys,srcObjectAIndexIMP,i);
|
|
NSString *destKey = GDL2_ObjectAtIndexWithImp(destinationKeys,dstObjectAIndexIMP,i);
|
|
id sourceValue = [relayedValues objectForKey: sourceKey];
|
|
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"sourceValue=%@",
|
|
sourceValue);
|
|
|
|
[relayedValues removeObjectForKey: sourceKey];
|
|
[relayedValues setObject: sourceValue
|
|
forKey: destKey];
|
|
}
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"relayedValues=%@",
|
|
relayedValues);
|
|
|
|
NSAssert1(destinationObject,
|
|
@"No destinationObject for call of "
|
|
@"recordUpdateForObject:changes: relayedValues: %@",
|
|
relayedValues);
|
|
|
|
[self recordUpdateForObject: destinationObject
|
|
changes: relayedValues];
|
|
}
|
|
else
|
|
{
|
|
//Verify !!
|
|
NSDictionary *destinationValues;
|
|
IMP srcObjectAIndexIMP=[sourceKeys methodForSelector: @selector(objectAtIndex:)];
|
|
IMP dstObjectAIndexIMP=[destinationKeys methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
//Now take destinationKeys values
|
|
destinationValues = [self valuesForKeys: destinationKeys
|
|
object: destinationObject];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"destinationValues=%@",
|
|
destinationValues);
|
|
//And put these values for source keys in the return object (sourceValues)
|
|
|
|
count = [destinationKeys count];
|
|
relayedValues = (NSMutableDictionary*)[NSMutableDictionary dictionary];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"relayedValues=%@",
|
|
relayedValues);
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
NSString *sourceKey = GDL2_ObjectAtIndexWithImp(sourceKeys,srcObjectAIndexIMP,i);
|
|
NSString *destinationKey = GDL2_ObjectAtIndexWithImp(destinationKeys,dstObjectAIndexIMP,i);
|
|
id destinationValue = [destinationValues
|
|
objectForKey: destinationKey];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"destinationKey=%@",
|
|
destinationKey);
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"destinationValue=%@", destinationValue);
|
|
|
|
if (!_isNilOrEONull(destinationValue))//?? or always
|
|
[relayedValues setObject: destinationValue
|
|
forKey: sourceKey];
|
|
}
|
|
//Put these values in source object database ope new row
|
|
NSDebugMLLog(@"EODatabaseContext", @"relayedValues=%@",
|
|
relayedValues);
|
|
|
|
[sourceNewRow takeValuesFromDictionary: relayedValues];
|
|
}
|
|
}
|
|
|
|
if ([sourceDBOpe databaseOperator] == EODatabaseNothingOperator)
|
|
{
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"Db Ope %@ for Nothing !!!", sourceDBOpe);
|
|
}
|
|
|
|
|
|
|
|
return relayedValues;
|
|
//Mirko Code:
|
|
/*
|
|
NSMutableArray *keys;
|
|
NSDictionary *values;
|
|
EOGlobalID *relationshipGID;
|
|
id objectTo;
|
|
|
|
objectTo = [object storedValueForKey:name];
|
|
relationshipGID = [_editingContext
|
|
globalIDForObject:objectTo];
|
|
if ([self ownsObject:objectTo] == YES)
|
|
{
|
|
for (h=0; h<count; h++)
|
|
{
|
|
join = [joins objectAtIndex:h];
|
|
|
|
value = [objectTo
|
|
storedValueForKey:
|
|
[[join destinationAttribute] name]];
|
|
|
|
if (value == nil)
|
|
value = GDL2_EONull;
|
|
|
|
[row setObject:value
|
|
forKey:[[join sourceAttribute] name]];
|
|
}
|
|
#if 0
|
|
inverse = [property inverseRelationship];
|
|
if (inverse)
|
|
{
|
|
toManySnapshot = AUTORELEASE([[self snapshotForSourceGlobalID:gid
|
|
relationshipName:[inverse name]]
|
|
mutableCopy]);
|
|
if (toManySnapshot == nil)
|
|
toManySnapshot = [NSMutableArray array];
|
|
|
|
[toManySnapshot addObject:gid];
|
|
|
|
[op recordToManySnapshot:toManySnapshot
|
|
relationshipName:name];
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
keys = [NSMutableArray arrayWithCapacity:count];
|
|
|
|
for (h=0; h<count; h++)
|
|
{
|
|
join = [joins objectAtIndex:h];
|
|
[keys addObject:[[join destinationAttribute]
|
|
name]];
|
|
}
|
|
|
|
values = [_coordinator valuesForKeys:keys
|
|
object:objectTo];
|
|
|
|
for (h=0; h<count; h++)
|
|
{
|
|
join = [joins objectAtIndex:h];
|
|
|
|
value = [values
|
|
objectForKey:
|
|
[[join destinationAttribute] name]];
|
|
|
|
if (value == nil)
|
|
value = GDL2_EONull;
|
|
|
|
[row setObject:value
|
|
forKey:[[join sourceAttribute] name]];
|
|
}
|
|
}
|
|
*/
|
|
|
|
};
|
|
|
|
- (void)recordDatabaseOperation: (EODatabaseOperation*)databaseOpe
|
|
{
|
|
//OK
|
|
EOGlobalID *gid = nil;
|
|
|
|
|
|
|
|
NSAssert(databaseOpe, @"No database operation");
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"_dbOperationsByGlobalID=%p",
|
|
_dbOperationsByGlobalID);
|
|
|
|
if (_dbOperationsByGlobalID)
|
|
{
|
|
//
|
|
NSDebugMLLog(@"EODatabaseContext", @"_dbOperationsByGlobalID=%@",
|
|
NSStringFromMapTable(_dbOperationsByGlobalID));
|
|
|
|
/*
|
|
// doesn't do this so some db operation are not recorded (when selecting objects)
|
|
if (!_dbOperationsByGlobalID)
|
|
_dbOperationsByGlobalID = NSCreateMapTable(NSObjectMapKeyCallBacks,
|
|
NSObjectMapValueCallBacks,
|
|
32);
|
|
*/
|
|
gid = [databaseOpe globalID];
|
|
|
|
|
|
|
|
NSMapInsert(_dbOperationsByGlobalID, gid, databaseOpe);
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"_dbOperationsByGlobalID=%p",
|
|
_dbOperationsByGlobalID);
|
|
NSDebugMLLog(@"EODatabaseContext", @"_dbOperationsByGlobalID=%@",
|
|
NSStringFromMapTable(_dbOperationsByGlobalID));
|
|
}
|
|
else
|
|
{
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"No _dbOperationsByGlobalID");
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (EODatabaseOperation*)databaseOperationForGlobalID: (EOGlobalID*)gid
|
|
{
|
|
//OK
|
|
EODatabaseOperation *dbOpe = nil;
|
|
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"_dbOperationsByGlobalID=%p",
|
|
_dbOperationsByGlobalID);
|
|
|
|
if (_dbOperationsByGlobalID)
|
|
{
|
|
//
|
|
NSDebugMLLog(@"EODatabaseContext", @"_dbOperationsByGlobalID=%@",
|
|
NSStringFromMapTable(_dbOperationsByGlobalID));
|
|
|
|
dbOpe = (EODatabaseOperation*)NSMapGet(_dbOperationsByGlobalID,
|
|
(const void*)gid);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return dbOpe;
|
|
}
|
|
|
|
- (EODatabaseOperation*)databaseOperationForObject: (id)object
|
|
{
|
|
//OK
|
|
EODatabaseOperation *databaseOpe = nil;
|
|
EOGlobalID *gid = nil;
|
|
|
|
NS_DURING // for trace purpose
|
|
{
|
|
|
|
|
|
if ([object isKindOfClass: [EOGenericRecord class]])
|
|
NSDebugMLLog(@"EODatabaseContext", @"dictionary=%@ ",
|
|
[object debugDictionaryDescription]);
|
|
|
|
gid = EODatabaseContext_globalIDForObjectWithImpPtr(self,NULL,object);
|
|
|
|
|
|
databaseOpe = [self databaseOperationForGlobalID: gid]; //OK
|
|
|
|
|
|
if (!databaseOpe)//OK
|
|
{
|
|
NSDictionary *snapshot = nil;
|
|
NSArray *classPropertyNames = nil;
|
|
NSArray *dbSnapshotKeys = nil;
|
|
int i = 0;
|
|
int propNamesCount = 0;
|
|
int snapKeyCount = 0;
|
|
NSMutableDictionary *row = nil;
|
|
NSMutableDictionary *newRow = nil;
|
|
EOEntity *entity = nil;
|
|
NSArray *primaryKeyAttributes = nil;
|
|
|
|
entity = [_database entityForObject: object]; //OK
|
|
NSDebugMLLog(@"EODatabaseContext", @"entity name=%@",
|
|
[entity name]);
|
|
|
|
primaryKeyAttributes = [entity primaryKeyAttributes]; //OK
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"primaryKeyAttributes=%@",
|
|
primaryKeyAttributes);
|
|
|
|
databaseOpe = [EODatabaseOperation
|
|
databaseOperationWithGlobalID: gid
|
|
object: object
|
|
entity: entity]; //OK
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"CREATED databaseOpe=%@\nfor object %p %@",
|
|
databaseOpe, object, object);
|
|
|
|
snapshot = EODatabaseContext_snapshotForGlobalIDWithImpPtr(self,NULL,gid);//OK
|
|
NSDebugMLLog(@"EODatabaseContext", @"snapshot %p=%@",
|
|
snapshot, snapshot);
|
|
|
|
if (!snapshot)
|
|
snapshot = [NSDictionary dictionary];
|
|
|
|
[databaseOpe setDBSnapshot: snapshot];
|
|
NSDebugMLLog(@"EODatabaseContext",@"object=%p databaseOpe=%@",
|
|
object,databaseOpe);
|
|
|
|
classPropertyNames = [entity classPropertyNames]; //OK (code, a3code, numcode, toLabel)
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"classPropertyNames=%@", classPropertyNames);
|
|
|
|
propNamesCount = [classPropertyNames count];
|
|
NSDebugMLLog(@"EODatabaseContext", @"propNamesCount=%d",
|
|
(int)propNamesCount);
|
|
|
|
//TODO: rewrite code: don't use temporary "row"
|
|
row = (NSMutableDictionary*)[NSMutableDictionary dictionary];
|
|
NSDebugMLLog(@"EODatabaseContext",@"object %p (class %@)=%@ ",
|
|
object,[object class],object);
|
|
|
|
/*if ([object isKindOfClass: [EOGenericRecord class]])
|
|
NSDebugMLLog(@"EODatabaseContext", @"dictionary=%@ ",
|
|
[object debugDictionaryDescription]);*/
|
|
if (propNamesCount>0)
|
|
{
|
|
IMP oaiIMP=[classPropertyNames methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i = 0; i < propNamesCount; i++)
|
|
{
|
|
id value = nil;
|
|
NSString *key = GDL2_ObjectAtIndexWithImp(classPropertyNames,oaiIMP,i);
|
|
|
|
|
|
|
|
/*NO !!
|
|
if ([attribute isKindOfClass:[EOAttribute class]] == NO)
|
|
continue;
|
|
// if ([attribute isFlattened] == NO)
|
|
*/
|
|
value = [object storedValueForKey: key]; //OK
|
|
NSDebugMLLog(@"EODatabaseContext", @"key=%@ value=%@",
|
|
key, value);
|
|
|
|
if (!value)
|
|
{
|
|
value = GDL2_EONull;
|
|
|
|
[[[entity attributeNamed: key] validateValue: &value]
|
|
raise];
|
|
}
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"key=%@ value=%@",
|
|
key, value);
|
|
|
|
[row setObject: value
|
|
forKey: key];
|
|
}
|
|
};
|
|
|
|
newRow = [[NSMutableDictionary alloc]
|
|
initWithDictionary: snapshot
|
|
copyItems: NO];
|
|
|
|
|
|
|
|
dbSnapshotKeys = [entity dbSnapshotKeys]; //OK (numcode, code, a3code)
|
|
NSDebugMLLog(@"EODatabaseContext", @"dbSnapshotKeys=%@",
|
|
dbSnapshotKeys);
|
|
|
|
snapKeyCount = [dbSnapshotKeys count];
|
|
|
|
if (snapKeyCount>0)
|
|
{
|
|
IMP oaiIMP=[dbSnapshotKeys methodForSelector: @selector(objectAtIndex:)];
|
|
for (i = 0; i < snapKeyCount; i++)
|
|
{
|
|
id key = GDL2_ObjectAtIndexWithImp(dbSnapshotKeys,oaiIMP,i);
|
|
id value = [row objectForKey: key]; //Really this key ?
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"key=%@ value=%@",
|
|
key, value);
|
|
|
|
// NSAssert1(value,@"No value for %@",key);
|
|
|
|
if (value)
|
|
[newRow setObject: value
|
|
forKey: key];
|
|
}
|
|
};
|
|
|
|
|
|
|
|
[databaseOpe setNewRow: newRow];
|
|
[self recordDatabaseOperation: databaseOpe];
|
|
RELEASE(newRow);
|
|
}
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER;
|
|
|
|
|
|
|
|
return databaseOpe;
|
|
}
|
|
|
|
- (void)relayPrimaryKey: (NSDictionary*)pk
|
|
sourceObject: (id)sourceObject
|
|
destObject: (id)destObject
|
|
relationship: (EORelationship*)relationship
|
|
{
|
|
//OK
|
|
NSDictionary *relayedAttributes = nil;
|
|
EOEntity *destEntity = nil;
|
|
NSArray *destAttributes = nil;
|
|
NSArray *destAttributeNames = nil;
|
|
NSDictionary *keyValues = nil;
|
|
NSArray *values = nil;
|
|
int i, count;
|
|
BOOL nullPKValues = YES;
|
|
|
|
|
|
|
|
NSAssert3(destObject,
|
|
@"No destinationObject. pk=%@ relationship=%@ sourceObject=%@",
|
|
pk,relationship,sourceObject);
|
|
|
|
destAttributes = [relationship destinationAttributes];
|
|
|
|
|
|
destAttributeNames = [destAttributes resultsOfPerformingSelector:
|
|
@selector(name)];
|
|
|
|
keyValues = [self valuesForKeys: destAttributeNames
|
|
object: destObject];
|
|
|
|
|
|
values = [keyValues allValues];
|
|
|
|
|
|
//Now test if null values
|
|
count = [values count];
|
|
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[values methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i = 0; nullPKValues && i < count; i++)
|
|
nullPKValues = _isNilOrEONull(GDL2_ObjectAtIndexWithImp(values,oaiIMP,i));
|
|
};
|
|
|
|
if (nullPKValues)
|
|
{
|
|
relayedAttributes = [self relayAttributesInRelationship: relationship
|
|
sourceObject: sourceObject
|
|
destinationObject: destObject];
|
|
|
|
destEntity = [relationship destinationEntity];
|
|
|
|
[self relayPrimaryKey: relayedAttributes
|
|
object: destObject
|
|
entity: destEntity];
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (void)relayPrimaryKey: (NSDictionary*)pk
|
|
object: (id)object
|
|
entity: (EOEntity*)entity
|
|
{
|
|
//TODO check
|
|
NSArray *relationships = nil;
|
|
NSArray *classPropertyNames = nil;
|
|
EODatabaseOperation *dbOpe = nil;
|
|
NSDictionary *dbSnapshot = nil;
|
|
int i, count=0;
|
|
|
|
relationships = [entity relationships]; //OK
|
|
classPropertyNames = [entity classPropertyNames];
|
|
dbOpe = [self databaseOperationForObject: object];
|
|
|
|
if (!dbOpe) {
|
|
dbSnapshot = [NSDictionary dictionary];
|
|
} else {
|
|
dbSnapshot = [dbOpe dbSnapshot];
|
|
}
|
|
|
|
if (relationships) {
|
|
count = [relationships count];
|
|
}
|
|
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[relationships methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
EORelationship *relationship = GDL2_ObjectAtIndexWithImp(relationships,oaiIMP,i);
|
|
EORelationship *substRelationship = nil;
|
|
NSString *relName;
|
|
id storedValue;
|
|
id value = nil;
|
|
id snapshot = nil;
|
|
id comSnapshotValue = nil;
|
|
|
|
substRelationship = [relationship _substitutionRelationshipForRow: dbSnapshot];
|
|
|
|
if (!substRelationship) {
|
|
continue;
|
|
}
|
|
|
|
relName = [substRelationship name];
|
|
|
|
if ((![substRelationship propagatesPrimaryKey]) ||
|
|
(![classPropertyNames containsObject:relName]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
storedValue = [object storedValueForKey:relName];
|
|
if (!storedValue)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
snapshot = [self _currentCommittedSnapshotForObject: object];
|
|
comSnapshotValue = [snapshot objectForKey:relName];
|
|
|
|
// or use == ?
|
|
if ([storedValue isEqual:comSnapshotValue])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ([substRelationship isToMany])
|
|
{
|
|
// or use == ?
|
|
if ([comSnapshotValue isEqual: (NSArray*)storedValue] == NO)
|
|
{
|
|
#warning check!!
|
|
NSArray * storedValueArray = (NSArray*)storedValue;
|
|
NSUInteger x;
|
|
for (x = [storedValueArray count]; x > 0; x--)
|
|
{
|
|
[self relayPrimaryKey: pk
|
|
sourceObject: object
|
|
destObject: [storedValueArray objectAtIndex:x-1]
|
|
relationship: substRelationship];
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
|
|
// 1:1 relationships may be optional so we may have no value here
|
|
[self relayPrimaryKey: pk
|
|
sourceObject: object
|
|
destObject: value
|
|
relationship: substRelationship]; //this one ??
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
- (void) createAdaptorOperationsForDatabaseOperation: (EODatabaseOperation*)dbOpe
|
|
attributes: (NSArray*)attributes
|
|
{
|
|
EOEntity * entity = nil;
|
|
EODatabaseOperator dbOperator = EODatabaseNothingOperator;
|
|
NSDictionary * changedValues = nil;
|
|
|
|
NSAssert(dbOpe, @"No operation");
|
|
|
|
entity = [dbOpe entity]; //OK
|
|
dbOperator = [dbOpe databaseOperator]; //OK
|
|
EOAdaptorOperation * lockOperation = nil;
|
|
NSDictionary * dbSnapshot = nil;
|
|
|
|
EOQualifier *lockingQualifier = nil;
|
|
|
|
switch (dbOperator)
|
|
{
|
|
case EODatabaseNothingOperator:
|
|
break;
|
|
case EODatabaseInsertOperator:
|
|
{
|
|
EOAdaptorOperation * insertOp = nil;
|
|
EOStoredProcedure * insertProc = nil;
|
|
NSDictionary * writeAttrValues = nil;
|
|
|
|
changedValues = [dbOpe newRow];
|
|
writeAttrValues = [self valuesToWriteForAttributes:attributes
|
|
entity:entity
|
|
changedValues:changedValues];
|
|
if ([writeAttrValues count] < 1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
insertOp = [EOAdaptorOperation adaptorOperationWithEntity:entity];
|
|
insertProc = [entity storedProcedureForOperation:EOInsertProcedureOperation];
|
|
|
|
if (insertProc)
|
|
{
|
|
[insertOp setAdaptorOperator:EOAdaptorStoredProcedureOperator];
|
|
[insertOp setStoredProcedure:insertProc];
|
|
} else
|
|
{
|
|
[insertOp setAdaptorOperator:EOAdaptorInsertOperator];
|
|
}
|
|
[insertOp setChangedValues:writeAttrValues];
|
|
[dbOpe addAdaptorOperation:insertOp];
|
|
|
|
break;
|
|
}
|
|
case EODatabaseUpdateOperator:
|
|
{
|
|
NSArray * lockNonQualAttrs = nil;
|
|
NSArray * pkAttributes = nil;
|
|
NSArray * lockingAttributes = nil;
|
|
NSUInteger changeCount, lockNonQualCount, lockAttrCount;
|
|
|
|
changedValues = [dbOpe rowDiffsForAttributes:attributes];
|
|
lockNonQualAttrs = [self lockingNonQualifiableAttributes:attributes];
|
|
lockingAttributes = [self lockingAttributesForAttributes:attributes
|
|
entity:entity];
|
|
|
|
changeCount = [changedValues count];
|
|
lockNonQualCount = [lockNonQualAttrs count];
|
|
lockAttrCount = [lockingAttributes count];
|
|
|
|
// test if we have anything to do
|
|
|
|
if (((changeCount == 0)) && ((lockNonQualCount == 0)) && ((lockAttrCount == 0))) {
|
|
return;
|
|
}
|
|
|
|
pkAttributes = [self primaryKeyAttributesForAttributes:attributes
|
|
entity:entity];
|
|
|
|
dbSnapshot = [dbOpe dbSnapshot];
|
|
|
|
lockingQualifier = [self qualifierForLockingAttributes:lockingAttributes
|
|
primaryKeyAttributes:pkAttributes
|
|
entity:entity
|
|
snapshot:dbSnapshot];
|
|
|
|
if (((changeCount == 0)) || (lockNonQualCount > 0))
|
|
{
|
|
lockOperation = [EOAdaptorOperation adaptorOperationWithEntity:entity];
|
|
[lockOperation setAdaptorOperator:EOAdaptorLockOperator];
|
|
[lockOperation setQualifier:lockingQualifier];
|
|
[lockOperation setChangedValues:dbSnapshot];
|
|
|
|
if (lockNonQualCount > 0)
|
|
{
|
|
[lockOperation setAttributes:lockNonQualAttrs];
|
|
} else {
|
|
[lockOperation setAttributes:pkAttributes];
|
|
}
|
|
[dbOpe addAdaptorOperation:lockOperation];
|
|
}
|
|
if (changeCount > 0)
|
|
{
|
|
NSDictionary * writeAttrValues = nil;
|
|
|
|
writeAttrValues = [self valuesToWriteForAttributes:attributes
|
|
entity:entity
|
|
changedValues:changedValues];
|
|
if ([writeAttrValues count] > 0)
|
|
{
|
|
EOAdaptorOperation * updateOperation = nil;
|
|
|
|
updateOperation = [EOAdaptorOperation adaptorOperationWithEntity:entity];
|
|
[updateOperation setAdaptorOperator:EOAdaptorUpdateOperator];
|
|
[updateOperation setChangedValues:writeAttrValues];
|
|
[updateOperation setQualifier:lockingQualifier];
|
|
[dbOpe addAdaptorOperation:updateOperation];
|
|
}
|
|
}
|
|
|
|
}
|
|
break;
|
|
case EODatabaseDeleteOperator:
|
|
{
|
|
NSArray * pkAttributes = nil;
|
|
NSArray * lockingAttributes = nil;
|
|
NSArray * lockNonQualAttrs = nil;
|
|
EOAdaptorOperation * deleteOp = nil;
|
|
EOStoredProcedure * deleteProc = nil;
|
|
|
|
pkAttributes = [self primaryKeyAttributesForAttributes:attributes
|
|
entity:entity];
|
|
|
|
lockingAttributes = [self lockingAttributesForAttributes:attributes
|
|
entity:entity];
|
|
|
|
dbSnapshot = [dbOpe dbSnapshot];
|
|
|
|
lockingQualifier = [self qualifierForLockingAttributes:lockingAttributes
|
|
primaryKeyAttributes:pkAttributes
|
|
entity:entity
|
|
snapshot:dbSnapshot];
|
|
|
|
lockNonQualAttrs = [self lockingNonQualifiableAttributes:attributes];
|
|
|
|
if ([lockNonQualAttrs count] > 0)
|
|
{
|
|
lockOperation = [EOAdaptorOperation adaptorOperationWithEntity:entity];
|
|
[lockOperation setAdaptorOperator:EOAdaptorLockOperator]; /* 0 in EOF */
|
|
[lockOperation setQualifier:lockingQualifier];
|
|
[lockOperation setAttributes:lockNonQualAttrs];
|
|
[lockOperation setChangedValues:dbSnapshot];
|
|
[dbOpe addAdaptorOperation:lockOperation];
|
|
}
|
|
|
|
deleteOp = [EOAdaptorOperation adaptorOperationWithEntity:entity];
|
|
deleteProc = [entity storedProcedureForOperation:EODeleteProcedureOperation];
|
|
|
|
if (deleteProc)
|
|
{
|
|
[deleteOp setAdaptorOperator:EOAdaptorStoredProcedureOperator]; /* 4 */
|
|
[deleteOp setStoredProcedure:deleteProc];
|
|
[deleteOp setChangedValues:dbSnapshot];
|
|
} else {
|
|
[deleteOp setAdaptorOperator:EOAdaptorDeleteOperator]; /* 3 */
|
|
[deleteOp setQualifier:lockingQualifier];
|
|
}
|
|
[dbOpe addAdaptorOperation:deleteOp];
|
|
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
- (void) createAdaptorOperationsForDatabaseOperation: (EODatabaseOperation*)dbOpe
|
|
{
|
|
//OK for Update - Test for others
|
|
NSArray *attributesToSave = nil;
|
|
NSMutableArray *attributes = nil;
|
|
NSUInteger count=0;
|
|
EODatabaseOperator dbOperator = EODatabaseNothingOperator;
|
|
EOEntity *entity = [dbOpe entity];
|
|
NSDictionary *rowDiffs = nil;
|
|
|
|
[self processSnapshotForDatabaseOperation: dbOpe];
|
|
dbOperator = [dbOpe databaseOperator];
|
|
|
|
if (dbOperator == EODatabaseUpdateOperator)
|
|
{
|
|
rowDiffs = [dbOpe rowDiffs];
|
|
if ((!rowDiffs) || (([rowDiffs count] == 0)))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
attributesToSave = [entity _attributesToSave];
|
|
attributes = [NSMutableArray array];
|
|
|
|
count = [attributesToSave count];
|
|
if (count>0)
|
|
{
|
|
int i=0;
|
|
IMP attributesAddObjectIMP=[attributes methodForSelector:@selector(addObject:)];
|
|
IMP attributesToSaveObjectAtIndexIMP=[attributesToSave methodForSelector:@selector(objectAtIndex:)];
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
EOAttribute *attribute =
|
|
GDL2_ObjectAtIndexWithImp(attributesToSave,attributesToSaveObjectAtIndexIMP,i);
|
|
|
|
|
|
|
|
if (![attribute isFlattened] && ![attribute isDerived]) //VERIFY
|
|
{
|
|
GDL2_AddObjectWithImp(attributes,attributesAddObjectIMP,attribute);
|
|
|
|
if ([rowDiffs objectForKey: [attribute name]]
|
|
&& [attribute isReadOnly])
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO: excption ???
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
[self createAdaptorOperationsForDatabaseOperation: dbOpe
|
|
attributes: attributes];
|
|
}
|
|
|
|
static NSComparisonResult
|
|
compareUsingEntityNames(id left, id right, void* vpSortOrders)
|
|
{
|
|
NSArray * sortOrders = (NSArray *)vpSortOrders;
|
|
EOAdaptorOperation * leftOp = (EOAdaptorOperation*) left;
|
|
EOAdaptorOperation * rightOp = (EOAdaptorOperation*) right;
|
|
NSString * leftName = [[leftOp entity] name];
|
|
NSString * rightName = [[rightOp entity] name];
|
|
NSUInteger leftIndex;
|
|
NSUInteger rightIndex;
|
|
|
|
leftIndex = [sortOrders indexOfObject:leftName];
|
|
rightIndex = [sortOrders indexOfObject:rightName];
|
|
|
|
if (leftIndex < rightIndex) {
|
|
return NSOrderedAscending;
|
|
}
|
|
if (leftIndex > rightIndex) {
|
|
return NSOrderedDescending;
|
|
} else {
|
|
EOAdaptorOperator leftOperator = [leftOp adaptorOperator];
|
|
EOAdaptorOperator rightOperator = [rightOp adaptorOperator];
|
|
|
|
if (leftOperator < rightOperator) {
|
|
return NSOrderedAscending;
|
|
}
|
|
if (leftOperator > rightOperator) {
|
|
return NSOrderedDescending;
|
|
}
|
|
}
|
|
|
|
return NSOrderedSame;
|
|
}
|
|
|
|
- (NSArray*) orderAdaptorOperations
|
|
{
|
|
NSMutableArray * orderedAdaptorOpe = nil;
|
|
|
|
if (_delegateRespondsTo.willOrderAdaptorOperations == YES) {
|
|
orderedAdaptorOpe = (NSMutableArray*) [_delegate databaseContext: self
|
|
willOrderAdaptorOperationsFromDatabaseOperations:
|
|
NSAllMapTableValues(_dbOperationsByGlobalID)];
|
|
|
|
} else {
|
|
NSMutableArray * entities = [NSMutableArray array];
|
|
// NSMutableArray * adaptorOperations = [NSMutableArray array];
|
|
NSMapEnumerator dbOpeEnum;
|
|
EOGlobalID * gid = nil;
|
|
EODatabaseOperation * dbOpe = nil;
|
|
NSArray * entityNameOrderingArray = nil;
|
|
// NSHashTable * entitiesHashTable = NSCreateHashTable(NSNonOwnedPointerHashCallBacks,32);
|
|
|
|
orderedAdaptorOpe = (NSMutableArray*)[NSMutableArray array];
|
|
|
|
dbOpeEnum = NSEnumerateMapTable(_dbOperationsByGlobalID);
|
|
|
|
while (NSNextMapEnumeratorPair(&dbOpeEnum, (void **)&gid,
|
|
(void **)&dbOpe))
|
|
{
|
|
NSArray *dbOpeAdaptorOperations = [dbOpe adaptorOperations];
|
|
NSUInteger operCount = 0;
|
|
NSUInteger idx = 0;
|
|
|
|
if (dbOpeAdaptorOperations)
|
|
{
|
|
[orderedAdaptorOpe addObjectsFromArray:dbOpeAdaptorOperations];
|
|
operCount = [dbOpeAdaptorOperations count];
|
|
}
|
|
|
|
|
|
while (idx < operCount)
|
|
{
|
|
EOEntity * entity = [[dbOpeAdaptorOperations objectAtIndex:idx] entity];
|
|
if (([entities indexOfObject:entity] == NSNotFound))
|
|
{
|
|
[entities addObject:entity];
|
|
}
|
|
idx++;
|
|
}
|
|
|
|
}
|
|
NSEndMapTableEnumeration(&dbOpeEnum);
|
|
|
|
entityNameOrderingArray = [self entityNameOrderingArrayForEntities:entities];
|
|
|
|
[orderedAdaptorOpe sortUsingFunction:compareUsingEntityNames
|
|
context:entityNameOrderingArray];
|
|
}
|
|
|
|
return orderedAdaptorOpe;
|
|
}
|
|
|
|
- (NSArray*) entitiesOnWhichThisEntityDepends: (EOEntity*)entity
|
|
{
|
|
NSMutableArray *entities = nil;
|
|
NSArray *relationships = nil;
|
|
int count;
|
|
|
|
|
|
|
|
relationships = [entity relationships];
|
|
count = [relationships count];
|
|
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[relationships methodForSelector: @selector(objectAtIndex:)];
|
|
int i=0;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
EORelationship *relationship = GDL2_ObjectAtIndexWithImp(relationships,oaiIMP,i);
|
|
|
|
|
|
|
|
if (![relationship isToMany]) //If to many: do nothing
|
|
{
|
|
if ([relationship isFlattened])
|
|
{
|
|
//TODO VERIFY
|
|
EOExpressionArray *definitionArray=[relationship _definitionArray];
|
|
EORelationship *firstRelationship=[definitionArray objectAtIndex:0];
|
|
EOEntity *firstDefEntity=[firstRelationship destinationEntity];
|
|
NSArray *defDependEntities=[self
|
|
entitiesOnWhichThisEntityDepends:firstDefEntity];
|
|
if ([defDependEntities count]>0)
|
|
{
|
|
if (!entities)
|
|
entities = [NSMutableArray array];
|
|
|
|
[entities addObjectsFromArray: defDependEntities];
|
|
};
|
|
}
|
|
else
|
|
{
|
|
//Here ??
|
|
EOEntity *destinationEntity = [relationship destinationEntity];
|
|
EORelationship *inverseRelationship = [relationship
|
|
anyInverseRelationship];
|
|
|
|
if ([inverseRelationship isToMany])
|
|
{
|
|
//Do nothing ?
|
|
}
|
|
else
|
|
{
|
|
if ([inverseRelationship propagatesPrimaryKey])
|
|
{
|
|
//OK
|
|
if (!entities)
|
|
entities = [NSMutableArray array];
|
|
|
|
[entities addObject: destinationEntity];
|
|
}
|
|
else
|
|
{
|
|
if ([inverseRelationship ownsDestination])
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return entities;
|
|
}
|
|
|
|
/* this is private. it seems other implementations are doing much more
|
|
* than this.
|
|
*/
|
|
|
|
- (NSArray*)entityNameOrderingArrayForEntities: (NSArray*)entities
|
|
{
|
|
//TODO
|
|
NSMutableArray *ordering = [NSMutableArray array];
|
|
NSMutableSet *orderedEntities = [NSMutableSet set];
|
|
/*EODatabase *database = [self database];
|
|
NSArray *models = [database models];*/
|
|
NSMutableDictionary *dependsDict = [NSMutableDictionary dictionary];
|
|
int count = [entities count];
|
|
|
|
//TODO NSArray* originalOrdering=...
|
|
/*TODO for each mdoel:
|
|
userInfo (ret nil)
|
|
*/
|
|
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[entities methodForSelector: @selector(objectAtIndex:)];
|
|
int i=0;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
//OK
|
|
EOEntity *entity=GDL2_ObjectAtIndexWithImp(entities,oaiIMP,i);
|
|
NSArray *dependsEntities = [self
|
|
entitiesOnWhichThisEntityDepends: entity];
|
|
|
|
if ([dependsEntities count])
|
|
[dependsDict setObject: dependsEntities
|
|
forKey: [entity name]];
|
|
}
|
|
|
|
ordering = [NSMutableArray array];
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
EOEntity *entity=GDL2_ObjectAtIndexWithImp(entities,oaiIMP,i);
|
|
[self insertEntity: entity
|
|
intoOrderingArray: ordering
|
|
withDependencies: dependsDict
|
|
processingSet: orderedEntities];
|
|
}
|
|
}
|
|
//TODO
|
|
/*
|
|
model userInfo //ret nil
|
|
setUserInfo: {EOEntityOrdering = ordering; }
|
|
*/
|
|
|
|
return ordering;
|
|
}
|
|
|
|
- (BOOL) isValidQualifierTypeForAttribute: (EOAttribute*)attribute
|
|
{
|
|
//OK
|
|
BOOL isValid = NO;
|
|
EOEntity *entity = nil;
|
|
EOModel *model = nil;
|
|
EODatabase *database = nil;
|
|
EOAdaptor *adaptor = nil;
|
|
NSString *externalType = nil;
|
|
|
|
entity = [attribute entity];
|
|
|
|
NSAssert1(entity, @"No entity for attribute %@", attribute);
|
|
|
|
model = [entity model];
|
|
database = [self database];
|
|
adaptor = [database adaptor];
|
|
externalType = [attribute externalType];
|
|
isValid = [adaptor isValidQualifierType: externalType
|
|
model: model];
|
|
|
|
return isValid;
|
|
}
|
|
|
|
- (NSArray*) lockingNonQualifiableAttributes: (NSArray*)attributes
|
|
{
|
|
NSMutableArray * lockingAttrs = nil;
|
|
NSArray * attributesUsedForLocking = nil;
|
|
NSUInteger i = [attributes count];
|
|
|
|
for (; i > 0; i--)
|
|
{
|
|
EOAttribute * attr = [attributes objectAtIndex:i-1];
|
|
|
|
if (!attributesUsedForLocking)
|
|
{
|
|
attributesUsedForLocking = [[attr entity] attributesUsedForLocking];
|
|
}
|
|
|
|
if ([self isValidQualifierTypeForAttribute:attr] ||
|
|
(![attributesUsedForLocking containsObject:attr]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!lockingAttrs)
|
|
{
|
|
lockingAttrs = [NSMutableArray array];
|
|
}
|
|
[lockingAttrs addObject:attr];
|
|
}
|
|
|
|
if (!lockingAttrs) {
|
|
lockingAttrs = [NSArray array];
|
|
}
|
|
|
|
return lockingAttrs;
|
|
}
|
|
|
|
|
|
- (NSArray*) lockingAttributesForAttributes: (NSArray*)attributes
|
|
entity: (EOEntity*)entity
|
|
{
|
|
//TODO
|
|
NSArray *retAttributes = nil;
|
|
int count = 0;
|
|
NSArray *attributesUsedForLocking = nil;
|
|
|
|
|
|
|
|
attributesUsedForLocking = [entity attributesUsedForLocking];
|
|
count = [attributes count];
|
|
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[attributes methodForSelector: @selector(objectAtIndex:)];
|
|
int i=0;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
id attribute = GDL2_ObjectAtIndexWithImp(attributes,oaiIMP,i);
|
|
//do this on 1st only
|
|
BOOL isFlattened = [attribute isFlattened];
|
|
|
|
if (isFlattened)
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
else
|
|
{
|
|
NSArray *rootAttributesUsedForLocking = [entity rootAttributesUsedForLocking];
|
|
|
|
retAttributes = rootAttributesUsedForLocking;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
return retAttributes; //TODO
|
|
}
|
|
|
|
- (NSArray*) primaryKeyAttributesForAttributes: (NSArray*)attributes
|
|
entity: (EOEntity*)entity
|
|
{
|
|
//TODO
|
|
NSArray *retAttributes = nil;
|
|
int count = 0;
|
|
|
|
|
|
//TODO
|
|
|
|
count = [attributes count];
|
|
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[attributes methodForSelector: @selector(objectAtIndex:)];
|
|
int i=0;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
id attribute = GDL2_ObjectAtIndexWithImp(attributes,oaiIMP,i);
|
|
BOOL isFlattened = [attribute isFlattened];
|
|
|
|
//call isFlattened on 1st only
|
|
if (isFlattened)
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
else
|
|
{
|
|
NSArray *primaryKeyAttributes = [entity primaryKeyAttributes];
|
|
|
|
retAttributes = primaryKeyAttributes;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
return retAttributes;
|
|
}
|
|
|
|
- (EOQualifier*) qualifierForLockingAttributes: (NSArray*)attributes
|
|
primaryKeyAttributes: (NSArray*)primaryKeyAttributes
|
|
entity: (EOEntity*)entity
|
|
snapshot: (NSDictionary*)snapshot
|
|
{
|
|
//OK
|
|
EOQualifier *qualifier = nil;
|
|
NSMutableArray *qualifiers = nil;
|
|
int which;
|
|
|
|
//First use primaryKeyAttributes, next use attributes
|
|
for (which = 0; which < 2; which++)
|
|
{
|
|
NSArray *array = (which == 0 ? primaryKeyAttributes : attributes);
|
|
NSUInteger i,count = [array count];
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
EOAttribute *attribute = [array objectAtIndex: i];
|
|
|
|
if (which == 0 || ![primaryKeyAttributes containsObject: attribute])// Test if we haven't already processed it
|
|
{
|
|
if (![self isValidQualifierTypeForAttribute: attribute])
|
|
{
|
|
NSLog(@"Invalid externalType for attribute '%@' of entity named '%@' - model '%@'",
|
|
[attribute name], [[attribute entity] name],
|
|
[[[attribute entity] model] name]);
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
else
|
|
{
|
|
NSString *attributeName = nil;
|
|
NSString *snapName = nil;
|
|
id value = nil;
|
|
EOQualifier *aQualifier = nil;
|
|
|
|
attributeName = [attribute name];
|
|
NSAssert1(attributeName, @"no attribute name for attribute %@", attribute);
|
|
|
|
snapName = [entity snapshotKeyForAttributeName: attributeName];
|
|
NSAssert2(snapName, @"no snapName for attribute %@ in entity %@",
|
|
attributeName, [entity name]);
|
|
|
|
value = [snapshot objectForKey:snapName];
|
|
|
|
NSAssert4(value != nil,
|
|
@"no value for snapshotKey '%@' in snapshot (address=%p) %@ for entity %@",
|
|
snapName, snapshot, snapshot, [entity name]);
|
|
|
|
aQualifier
|
|
= [EOKeyValueQualifier qualifierWithKey: attributeName
|
|
operatorSelector: @selector(isEqualTo:)
|
|
value: value];
|
|
|
|
if (!qualifiers)
|
|
qualifiers = [NSMutableArray array];
|
|
|
|
[qualifiers addObject: aQualifier];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ([qualifiers count] == 1)
|
|
{
|
|
qualifier = [qualifiers objectAtIndex: 0];
|
|
}
|
|
else
|
|
{
|
|
qualifier = [EOAndQualifier qualifierWithQualifierArray: qualifiers];
|
|
}
|
|
|
|
return qualifier;
|
|
}
|
|
|
|
- (void) insertEntity: (EOEntity*)entity
|
|
intoOrderingArray: (NSMutableArray*)orderingArray
|
|
withDependencies: (NSDictionary*)dependencies
|
|
processingSet: (NSMutableSet*)processingSet
|
|
{
|
|
NSString * entityName = [entity name];
|
|
|
|
if ((([orderingArray indexOfObject:entityName] == NSNotFound)) &&
|
|
(![processingSet containsObject:entityName]))
|
|
{
|
|
NSUInteger depCount = 0;
|
|
NSUInteger idx = 0;
|
|
NSArray * dependenciesForEntity = [dependencies objectForKey:entityName];
|
|
|
|
[processingSet addObject:entityName];
|
|
|
|
if (dependenciesForEntity) {
|
|
depCount = [dependenciesForEntity count];
|
|
}
|
|
|
|
for (; idx < depCount; idx++)
|
|
{
|
|
[self insertEntity:[dependenciesForEntity objectAtIndex:idx]
|
|
intoOrderingArray:orderingArray
|
|
withDependencies:dependencies
|
|
processingSet:processingSet];
|
|
}
|
|
|
|
[orderingArray addObject:entityName];
|
|
}
|
|
}
|
|
|
|
- (void) processSnapshotForDatabaseOperation: (EODatabaseOperation*)dbOpe
|
|
{
|
|
//Near OK
|
|
EOAdaptor *adaptor = [_database adaptor];//OK
|
|
EOEntity *entity = [dbOpe entity];//OK
|
|
NSMutableDictionary *newRow = nil;
|
|
NSDictionary *dbSnapshot = nil;
|
|
NSEnumerator *attrNameEnum = nil;
|
|
id attrName = nil;
|
|
IMP enumNO=NULL; // nextObject
|
|
|
|
newRow = [dbOpe newRow]; //OK{a3code = Q77; code = Q7; numcode = 007; } //ALLOK
|
|
|
|
dbSnapshot = [dbOpe dbSnapshot];
|
|
|
|
// we need to make sure that we do not change an array while enumering it.
|
|
attrNameEnum = [[NSArray arrayWithArray:[newRow allKeys]] objectEnumerator];
|
|
enumNO=NULL;
|
|
while ((attrName = GDL2_NextObjectWithImpPtr(attrNameEnum,&enumNO)))
|
|
{
|
|
EOAttribute *attribute = [entity attributeNamed: attrName];
|
|
id newRowValue = nil;
|
|
id dbSnapshotValue = nil;
|
|
|
|
newRowValue = [newRow objectForKey:attrName];
|
|
|
|
|
|
dbSnapshotValue = [dbSnapshot objectForKey: attrName];
|
|
|
|
if (dbSnapshotValue && (![newRowValue isEqual: dbSnapshotValue]))
|
|
{
|
|
id adaptorValue = [adaptor fetchedValueForValue: newRowValue
|
|
attribute: attribute];
|
|
|
|
if ((!adaptorValue) || ((adaptorValue != dbSnapshotValue) && (![adaptorValue isEqual:dbSnapshotValue])))
|
|
{
|
|
if (!adaptorValue)
|
|
{
|
|
adaptorValue = GDL2_EONull;
|
|
}
|
|
[newRow setObject:adaptorValue
|
|
forKey:attrName];
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
- (NSDictionary*) valuesToWriteForAttributes: (NSArray*)attributes
|
|
entity: (EOEntity*)entity
|
|
changedValues: (NSDictionary*)changedValues
|
|
{
|
|
//NEAR OK
|
|
NSMutableDictionary *valuesToWrite = [NSMutableDictionary dictionary];
|
|
BOOL isReadOnlyEntity = NO;
|
|
|
|
isReadOnlyEntity = [entity isReadOnly];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"isReadOnlyEntity=%s",
|
|
(isReadOnlyEntity ? "YES" : "NO"));
|
|
|
|
if (isReadOnlyEntity)
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
else
|
|
{
|
|
int count = [attributes count];
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[attributes methodForSelector: @selector(objectAtIndex:)];
|
|
int i=0;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
EOAttribute *attribute = GDL2_ObjectAtIndexWithImp(attributes,oaiIMP,i);
|
|
BOOL isReadOnly = [attribute isReadOnly];
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"isReadOnly=%s",
|
|
(isReadOnly ? "YES" : "NO"));
|
|
|
|
if (isReadOnly)
|
|
{
|
|
NSEmitTODO();
|
|
NSDebugMLog(@"attribute=%@", attribute);
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
else
|
|
{
|
|
NSString *attrName = [attribute name];
|
|
NSString *snapName = nil;
|
|
id value = nil;
|
|
|
|
|
|
|
|
snapName = [entity snapshotKeyForAttributeName: attrName];
|
|
|
|
|
|
value = [changedValues objectForKey: snapName];
|
|
|
|
|
|
if (value)
|
|
[valuesToWrite setObject: value
|
|
forKey: attrName];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return valuesToWrite;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation EODatabaseContext(EOBatchFaulting)
|
|
|
|
- (void)batchFetchRelationship: (EORelationship *)relationship
|
|
forSourceObjects: (NSArray *)objects
|
|
editingContext: (EOEditingContext *)editingContext
|
|
{ // TODO
|
|
NSMutableArray *qualifierArray, *valuesArray, *toManySnapshotArray;
|
|
NSMutableDictionary *values;
|
|
NSArray *array;
|
|
NSEnumerator *objsEnum, *joinsEnum, *keyEnum;
|
|
NSString *key;
|
|
EOFetchSpecification *fetch;
|
|
EOQualifier *qualifier;
|
|
EOFault *fault;
|
|
EOJoin *join;
|
|
BOOL equal;
|
|
int i, count;
|
|
id object;
|
|
NSString* relationshipName = nil;
|
|
IMP globalIDForObjectIMP=NULL;
|
|
IMP toManySnapArrayObjectAtIndexIMP=NULL;
|
|
IMP objsEnumNO=NULL;
|
|
IMP objectsOAI=NULL;
|
|
|
|
qualifierArray = AUTORELEASE([GDL2_alloc(NSMutableArray) init]);
|
|
valuesArray = AUTORELEASE([GDL2_alloc(NSMutableArray) init]);
|
|
toManySnapshotArray = AUTORELEASE([GDL2_alloc(NSMutableArray) init]);
|
|
toManySnapArrayObjectAtIndexIMP=[toManySnapshotArray methodForSelector: @selector(objectAtIndex:)];
|
|
relationshipName = [relationship name];
|
|
|
|
objsEnum = [objects objectEnumerator];
|
|
objsEnumNO=NULL;
|
|
while ((object = GDL2_NextObjectWithImpPtr(objsEnum,&objsEnumNO)))
|
|
{
|
|
IMP joinsEnumNO=NO;
|
|
values
|
|
= AUTORELEASE([GDL2_alloc(NSMutableDictionary) initWithCapacity: 4]);
|
|
|
|
fault = [object valueForKey: relationshipName];
|
|
[EOFault clearFault: fault];
|
|
|
|
joinsEnum = [[relationship joins] objectEnumerator];
|
|
while ((join = GDL2_NextObjectWithImpPtr(joinsEnum,&joinsEnumNO)))
|
|
{
|
|
[values setObject: [object valueForKey: [[join sourceAttribute] name]]
|
|
forKey: [[join destinationAttribute] name]];
|
|
}
|
|
|
|
[valuesArray addObject: values];
|
|
[toManySnapshotArray addObject:
|
|
AUTORELEASE([GDL2_alloc(NSMutableArray) init])];
|
|
|
|
[qualifierArray addObject: [EOQualifier qualifierToMatchAllValues:
|
|
values]];
|
|
}
|
|
|
|
if ([qualifierArray count] == 1)
|
|
qualifier = [qualifierArray objectAtIndex: 0];
|
|
else
|
|
qualifier = [EOOrQualifier qualifierWithQualifierArray: qualifierArray];
|
|
|
|
fetch = [EOFetchSpecification fetchSpecificationWithEntityName:
|
|
[[relationship destinationEntity] name]
|
|
qualifier: qualifier
|
|
sortOrderings: nil];
|
|
|
|
array = [self objectsWithFetchSpecification: fetch
|
|
editingContext: editingContext];
|
|
|
|
count = [valuesArray count];
|
|
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[valuesArray methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
objsEnum = [array objectEnumerator];
|
|
objsEnumNO=NULL;
|
|
while ((object = GDL2_NextObjectWithImpPtr(objsEnum,&objsEnumNO)))
|
|
{
|
|
IMP objectVFK=NULL; // valueForKey:
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
IMP keyEnumNO=NULL; // nextObject
|
|
IMP valuesOFK=NULL; // objectForKey:
|
|
equal = YES;
|
|
values = GDL2_ObjectAtIndexWithImp(valuesArray,oaiIMP,i);
|
|
|
|
keyEnum = [values keyEnumerator];
|
|
while ((key = GDL2_NextObjectWithImpPtr(keyEnum,&keyEnumNO)))
|
|
{
|
|
if ([GDL2_ValueForKeyWithImpPtr(object,&objectVFK,key)
|
|
isEqual: GDL2_ObjectForKeyWithImpPtr(values,&valuesOFK,key)] == NO)
|
|
{
|
|
equal = NO;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (equal == YES)
|
|
{
|
|
EOGlobalID* gid = nil;
|
|
id snapshot = GDL2_ObjectAtIndexWithImp(toManySnapshotArray,toManySnapArrayObjectAtIndexIMP,i);
|
|
|
|
[[GDL2_ObjectAtIndexWithImpPtr(objects,&objectsOAI,i) valueForKey: relationshipName]
|
|
addObject: object];
|
|
|
|
gid=EOEditingContext_globalIDForObjectWithImpPtr(editingContext,&globalIDForObjectIMP,object);
|
|
|
|
[snapshot addObject: gid];
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==> see _registerSnapshot:forSourceGlobalID:relationshipName:editingContext:
|
|
|
|
if (count>0)
|
|
{
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
id snapshot = GDL2_ObjectAtIndexWithImp(toManySnapshotArray,toManySnapArrayObjectAtIndexIMP,i);
|
|
EOGlobalID* gid=EOEditingContext_globalIDForObjectWithImpPtr(editingContext,
|
|
&globalIDForObjectIMP,
|
|
GDL2_ObjectAtIndexWithImpPtr(objects,&objectsOAI,i));
|
|
[_database recordSnapshot: snapshot
|
|
forSourceGlobalID: gid
|
|
relationshipName: relationshipName];
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation EODatabaseContext (EODatabaseContextPrivate)
|
|
|
|
- (void) _fireArrayFault: (id)object
|
|
{
|
|
//OK ??
|
|
BOOL fetchIt = YES;
|
|
|
|
|
|
|
|
|
|
|
|
if (_delegateRespondsTo.shouldFetchObjectFault == YES)
|
|
fetchIt = [_delegate databaseContext: self
|
|
shouldFetchObjectFault: object];
|
|
|
|
if (fetchIt)
|
|
{
|
|
/*Class targetClass = Nil;
|
|
void *extraData = NULL;*/
|
|
EOAccessArrayFaultHandler *handler = (EOAccessArrayFaultHandler *)[EOFault handlerForFault:object];
|
|
EOEditingContext *context = [handler editingContext];
|
|
NSString *relationshipName= [handler relationshipName];
|
|
EOKeyGlobalID *gid = [handler sourceGlobalID];
|
|
NSArray *objects = nil;
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"relationshipName=%@",
|
|
relationshipName);
|
|
|
|
|
|
objects = [context objectsForSourceGlobalID: gid
|
|
relationshipName: relationshipName
|
|
editingContext: context];
|
|
|
|
if (_isFault(object))
|
|
[EOFault clearFault: object]; //??
|
|
/* in clearFault
|
|
[handler faultWillFire:object];
|
|
targetClass=[handler targetClass];
|
|
extraData=[handler extraData];
|
|
RELEASE(handler);
|
|
*/
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"NEAR FINISHED 1 object count=%d %p %@",
|
|
[object count],
|
|
object,
|
|
object);
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"NEAR FINISHED 1 objects count=%d %p %@",
|
|
[objects count],
|
|
objects,
|
|
objects);
|
|
|
|
if (objects != object)
|
|
{
|
|
//No, not needed [object removeObjectsInArray:objects];//Because some objects may be here. We don't want duplicate. It's a hack because I don't see why there's objects in object !
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"NEAR FINISHED 1 object count=%d %p %@",
|
|
[object count],
|
|
object,
|
|
object);
|
|
|
|
[object addObjectsFromArray: objects];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"NEAR FINISHED 2 object count=%d %@",
|
|
[object count],
|
|
object);
|
|
}
|
|
}
|
|
//END!
|
|
/*
|
|
}
|
|
|
|
- (void)_batchToMany:(id)fault
|
|
withHandler:(EOAccessArrayFaultHandler *)handler
|
|
{
|
|
*/
|
|
|
|
/*
|
|
EOAccessArrayFaultHandler *usedHandler, *firstHandler, *lastHandler;
|
|
EOAccessArrayFaultHandler *bufHandler;
|
|
NSMutableDictionary *batchBuffer;
|
|
EOEditingContext *context;
|
|
NSMutableArray *objects;
|
|
EOKeyGlobalID *gid;
|
|
EOEntity *entity;
|
|
EORelationship *relationship;
|
|
unsigned int maxBatch;
|
|
BOOL batch = YES, changeBatch = NO;
|
|
|
|
|
|
gid = [handler sourceGlobalID];//OK
|
|
context = [handler editingContext];//OK
|
|
|
|
entity = [_database entityNamed:[gid entityName]];//-done
|
|
relationship = [entity relationshipNamed:[handler relationshipName]];//-done
|
|
maxBatch = [relationship numberOfToManyFaultsToBatchFetch];//-done
|
|
|
|
batchBuffer = [_batchToManyFaultBuffer objectForKey:[entity name]];
|
|
bufHandler = [batchBuffer objectForKey:[relationship name]];
|
|
|
|
objects = [NSMutableArray array];
|
|
|
|
[objects addObject:[context objectForGlobalID:gid]];//-done
|
|
|
|
firstHandler = lastHandler = nil;
|
|
usedHandler = handler;
|
|
|
|
if (bufHandler && [bufHandler isEqual:usedHandler] == YES)
|
|
changeBatch = YES;
|
|
|
|
if (maxBatch > 1)
|
|
{
|
|
maxBatch--;
|
|
|
|
while (maxBatch--)
|
|
{
|
|
if (lastHandler == nil)
|
|
{
|
|
usedHandler = (EOAccessArrayFaultHandler *)[usedHandler
|
|
previous];
|
|
|
|
if (usedHandler)
|
|
firstHandler = usedHandler;
|
|
else
|
|
lastHandler = usedHandler = (EOAccessArrayFaultHandler *)
|
|
[handler next];
|
|
}
|
|
else
|
|
{
|
|
usedHandler = (EOAccessArrayFaultHandler *)[lastHandler next];
|
|
|
|
if (usedHandler)
|
|
lastHandler = usedHandler;
|
|
}
|
|
|
|
if (usedHandler == nil)
|
|
break;
|
|
|
|
if (bufHandler && [bufHandler isEqual:usedHandler] == YES)
|
|
changeBatch = YES;
|
|
|
|
[objects addObject:[context objectForGlobalID:[usedHandler
|
|
sourceGlobalID]]];
|
|
}
|
|
}
|
|
|
|
if (firstHandler == nil)
|
|
firstHandler = handler;
|
|
if (lastHandler == nil)
|
|
lastHandler = handler;
|
|
|
|
usedHandler = (id)[firstHandler previous];
|
|
bufHandler = (id)[lastHandler next];
|
|
if (usedHandler)
|
|
[usedHandler _linkNext:bufHandler];
|
|
|
|
usedHandler = bufHandler;
|
|
if (usedHandler)
|
|
{
|
|
[usedHandler _linkPrev:[firstHandler previous]];
|
|
if (bufHandler == nil)
|
|
bufHandler = usedHandler;
|
|
}
|
|
|
|
if (changeBatch == YES)
|
|
{
|
|
if (bufHandler)
|
|
[batchBuffer setObject:bufHandler
|
|
forKey:[relationship name]];
|
|
else
|
|
[batchBuffer removeObjectForKey:[relationship name]];
|
|
}
|
|
|
|
[self batchFetchRelationship:relationship
|
|
forSourceObjects:objects
|
|
editingContext:context];
|
|
*/
|
|
|
|
|
|
}
|
|
|
|
- (void) _fireFault: (id)object
|
|
{
|
|
//TODO
|
|
BOOL fetchIt = YES;//MIRKO
|
|
EOGlobalID *gid;
|
|
|
|
|
|
|
|
//MIRKO
|
|
NSDebugMLLog(@"EODatabaseContext",@"Fire Fault: object %p of class %@",
|
|
object,[object class]);
|
|
|
|
if (_delegateRespondsTo.shouldFetchObjectFault == YES)
|
|
{
|
|
fetchIt = [_delegate databaseContext: self
|
|
shouldFetchObjectFault: object];
|
|
}
|
|
|
|
if (fetchIt)
|
|
{
|
|
EOAccessFaultHandler *handler;
|
|
EOEditingContext *context;
|
|
NSDictionary *snapshot;
|
|
EOEntity *entity = nil;
|
|
NSString *entityName = nil;
|
|
|
|
handler = (EOAccessFaultHandler *)[EOFault handlerForFault: object];
|
|
context = [handler editingContext];
|
|
gid = [handler globalID];
|
|
snapshot = EODatabaseContext_snapshotForGlobalIDWithImpPtr(self,NULL,gid); //nil
|
|
|
|
if (snapshot)
|
|
{
|
|
//TODO _fireFault snapshot
|
|
NSEmitTODO();
|
|
// [self notImplemented: _cmd]; //TODO
|
|
}
|
|
|
|
entity = [self entityForGlobalID: gid];
|
|
entityName = [entity name];
|
|
|
|
if ([entity cachesObjects])
|
|
{
|
|
//TODO _fireFault [entity cachesObjects]
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
|
|
//??? generation # EOAccessGenericFaultHandler//ret 2
|
|
{
|
|
EOAccessFaultHandler *previousHandler;
|
|
EOAccessFaultHandler *nextHandler;
|
|
EOFetchSpecification *fetchSpecif;
|
|
NSArray *objects;
|
|
EOQualifier *qualifier;
|
|
/*int maxNumberOfInstancesToBatchFetch =
|
|
[entity maxNumberOfInstancesToBatchFetch];
|
|
NSDictionary *snapshot = [self snapshotForGlobalID: gid];*///nil //TODO use it !
|
|
NSDictionary *pk = [entity primaryKeyForGlobalID: (EOKeyGlobalID *)gid];
|
|
EOQualifier *pkQualifier = [entity qualifierForPrimaryKey: pk];
|
|
NSMutableArray *qualifiers = [NSMutableArray array];
|
|
|
|
[qualifiers addObject: pkQualifier];
|
|
|
|
previousHandler = (EOAccessFaultHandler *)[handler previous];
|
|
nextHandler = (EOAccessFaultHandler *)[handler next]; //nil
|
|
|
|
fetchSpecif = AUTORELEASE([EOFetchSpecification new]);
|
|
[fetchSpecif setEntityName: entityName];
|
|
|
|
qualifier = [EOOrQualifier qualifierWithQualifierArray: qualifiers];
|
|
|
|
[fetchSpecif setQualifier: qualifier];
|
|
|
|
objects = [self objectsWithFetchSpecification: fetchSpecif
|
|
editingContext: context];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"objects %p=%@ class=%@",
|
|
objects, objects, [objects class]);
|
|
}
|
|
}
|
|
|
|
if (_isFault(object)) {
|
|
[EOFault clearFault: object];
|
|
|
|
if ((!_delegate) || (!_delegateRespondsTo.databaseContextFailedToFetchObject) ||
|
|
(![_delegate databaseContext:self
|
|
failedToFetchObject:object
|
|
globalID:gid]))
|
|
{
|
|
[_missingObjectGIDs addObject:gid];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
- (void)_batchToOne:(id)fault
|
|
withHandler:(EOAccessFaultHandler *)handler
|
|
{
|
|
EOAccessFaultHandler *usedHandler, *firstHandler, *lastHandler;
|
|
EOAccessFaultHandler *bufHandler;
|
|
EOFetchSpecification *fetch;
|
|
EOEditingContext *context;
|
|
EOKeyGlobalID *gid;
|
|
EOQualifier *qualifier;
|
|
EOEntity *entity;
|
|
NSMutableArray *qualifierArray;
|
|
BOOL batch = YES, changeBatch = NO;
|
|
unsigned int maxBatch;
|
|
|
|
if (_delegateRespondsTo.shouldFetchObjectFault == YES)//-done
|
|
batch = [_delegate databaseContext:self
|
|
shouldFetchObjectFault:fault];//-done
|
|
|
|
if (batch == NO)//-done
|
|
return;//-done
|
|
|
|
gid = [handler globalID];//-done
|
|
context = [handler editingContext];//-done
|
|
|
|
entity = [_database entityNamed:[gid entityName]];//-done
|
|
maxBatch = [entity maxNumberOfInstancesToBatchFetch];//-done
|
|
|
|
bufHandler = [_batchFaultBuffer objectForKey:[entity name]];
|
|
|
|
firstHandler = lastHandler = nil;
|
|
usedHandler = handler;
|
|
|
|
if (bufHandler && [bufHandler isEqual:usedHandler] == YES)
|
|
changeBatch = YES;
|
|
|
|
if (maxBatch <= 1)
|
|
{
|
|
qualifier = [entity qualifierForPrimaryKey:
|
|
[entity primaryKeyForGlobalID:gid]];
|
|
}
|
|
else
|
|
{
|
|
qualifierArray = [NSMutableArray array];
|
|
|
|
[qualifierArray addObject:
|
|
[entity qualifierForPrimaryKey:
|
|
[entity primaryKeyForGlobalID:gid]]];
|
|
|
|
maxBatch--;
|
|
|
|
while (maxBatch--)
|
|
{
|
|
if (lastHandler == nil)
|
|
{
|
|
usedHandler = (EOAccessFaultHandler *)[usedHandler previous];
|
|
|
|
if (usedHandler)
|
|
firstHandler = usedHandler;
|
|
else
|
|
lastHandler = usedHandler = (EOAccessFaultHandler *)[handler
|
|
next];
|
|
}
|
|
else
|
|
{
|
|
usedHandler = (EOAccessFaultHandler *)[lastHandler next];
|
|
|
|
if (usedHandler)
|
|
lastHandler = usedHandler;
|
|
}
|
|
|
|
if (usedHandler == nil)
|
|
break;
|
|
|
|
if (changeBatch == NO &&
|
|
bufHandler && [bufHandler isEqual:usedHandler] == YES)
|
|
changeBatch = YES;
|
|
|
|
[qualifierArray addObject:
|
|
[entity qualifierForPrimaryKey:
|
|
[entity primaryKeyForGlobalID:
|
|
[usedHandler globalID]]]];
|
|
}
|
|
|
|
qualifier = AUTORELEASE([[EOOrQualifier alloc]
|
|
initWithQualifierArray:qualifierArray]);
|
|
}
|
|
|
|
if (firstHandler == nil)
|
|
firstHandler = handler;
|
|
if (lastHandler == nil)
|
|
lastHandler = handler;
|
|
|
|
usedHandler = (id)[firstHandler previous];
|
|
bufHandler = (id)[lastHandler next];
|
|
if (usedHandler)
|
|
[usedHandler _linkNext:bufHandler];
|
|
|
|
usedHandler = bufHandler;
|
|
if (usedHandler)
|
|
{
|
|
[usedHandler _linkPrev:[firstHandler previous]];
|
|
if (bufHandler == nil)
|
|
bufHandler = usedHandler;
|
|
}
|
|
|
|
if (changeBatch == YES)
|
|
{
|
|
if (bufHandler)
|
|
[_batchFaultBuffer setObject:bufHandler
|
|
forKey:[entity name]];
|
|
else
|
|
[_batchFaultBuffer removeObjectForKey:[entity name]];
|
|
}
|
|
|
|
fetch = [EOFetchSpecification fetchSpecificationWithEntityName:[entity name]
|
|
qualifier:qualifier
|
|
sortOrderings:nil];//-done
|
|
|
|
[context objectsWithFetchSpecification:fetch];//-done
|
|
}*/
|
|
|
|
|
|
// Clear all the faults for the relationship pointed by the source objects and
|
|
// make sure to perform only a single, efficient, fetch (two fetches if the
|
|
// relationship is many to many).
|
|
|
|
- (void)_addBatchForGlobalID: (EOKeyGlobalID *)globalID
|
|
fault: (EOFault *)fault
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
if (fault)
|
|
{
|
|
EOAccessGenericFaultHandler *handler = nil;
|
|
NSString *entityName = [globalID entityName];
|
|
|
|
|
|
|
|
handler = [_batchFaultBuffer objectForKey: entityName];
|
|
|
|
|
|
|
|
if (handler)
|
|
{
|
|
[(EOAccessGenericFaultHandler *)
|
|
[EOFault handlerForFault: fault]
|
|
linkAfter: handler
|
|
usingGeneration: [handler generation]];
|
|
}
|
|
else
|
|
{
|
|
handler = (EOAccessGenericFaultHandler *)[EOFault handlerForFault:
|
|
fault];
|
|
|
|
NSAssert1(handler, @"No handler for fault:%@", fault);
|
|
|
|
[_batchFaultBuffer setObject: handler
|
|
forKey: entityName];
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (void)_removeBatchForGlobalID: (EOKeyGlobalID *)globalID
|
|
fault: (EOFault *)fault
|
|
{
|
|
EOAccessGenericFaultHandler *handler, *prevHandler, *nextHandler;
|
|
NSString *entityName = [globalID entityName];
|
|
|
|
handler = (EOAccessGenericFaultHandler *)[EOFault handlerForFault: fault];
|
|
|
|
prevHandler = [handler previous];
|
|
nextHandler = [handler next];
|
|
|
|
if (prevHandler)
|
|
[prevHandler _linkNext: nextHandler];
|
|
if (nextHandler)
|
|
[nextHandler _linkPrev: prevHandler];
|
|
|
|
if ([_batchFaultBuffer objectForKey: entityName] == handler)
|
|
{
|
|
if (prevHandler)
|
|
[_batchFaultBuffer setObject: prevHandler
|
|
forKey: entityName];
|
|
else if (nextHandler)
|
|
[_batchFaultBuffer setObject: nextHandler
|
|
forKey: entityName];
|
|
else
|
|
[_batchFaultBuffer removeObjectForKey: entityName];
|
|
}
|
|
}
|
|
|
|
- (void)_addToManyBatchForSourceGlobalID: (EOKeyGlobalID *)globalID
|
|
relationshipName: (NSString *)relationshipName
|
|
fault: (EOFault *)fault
|
|
{
|
|
if (fault)
|
|
{
|
|
NSMutableDictionary *buf;
|
|
EOAccessGenericFaultHandler *handler;
|
|
NSString *entityName = [globalID entityName];
|
|
|
|
buf = [_batchToManyFaultBuffer objectForKey: entityName];
|
|
|
|
if (buf == nil)
|
|
{
|
|
buf = [NSMutableDictionary dictionaryWithCapacity: 8];
|
|
[_batchToManyFaultBuffer setObject: buf
|
|
forKey: entityName];
|
|
}
|
|
|
|
handler = [buf objectForKey: relationshipName];
|
|
|
|
if (handler)
|
|
{
|
|
[(EOAccessGenericFaultHandler *)
|
|
[EOFault handlerForFault: fault]
|
|
linkAfter: handler
|
|
usingGeneration: [handler generation]];
|
|
}
|
|
else
|
|
[buf setObject: [EOFault handlerForFault: fault]
|
|
forKey: relationshipName];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation EODatabaseContext (EODatabaseSnapshotting)
|
|
|
|
- (void)recordSnapshot: (NSDictionary *)snapshot
|
|
forGlobalID: (EOGlobalID *)gid
|
|
{
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"self=%p database=%p",
|
|
self, _database);
|
|
NSDebugMLLog(@"EODatabaseContext", @"self=%p _uniqueStack %p=%@",
|
|
self, _uniqueStack, _uniqueStack);
|
|
|
|
if ([_uniqueStack count] > 0)
|
|
{
|
|
NSMutableDictionary *snapshots = [_uniqueStack lastObject];
|
|
|
|
[snapshots setObject: snapshot
|
|
forKey: gid];
|
|
}
|
|
else
|
|
{
|
|
NSEmitTODO();
|
|
NSWarnLog(@"_uniqueStack is empty. May be there's no runing transaction !");
|
|
|
|
[self notImplemented: _cmd]; //TODO
|
|
}
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"self=%p _uniqueStack %p=%@",
|
|
self, _uniqueStack, _uniqueStack);
|
|
|
|
|
|
}
|
|
|
|
- (NSDictionary *)snapshotForGlobalID: (EOGlobalID *)gid
|
|
{
|
|
return [self snapshotForGlobalID: gid
|
|
after: EODistantPastTimeInterval];
|
|
}
|
|
|
|
- (NSDictionary *)snapshotForGlobalID: (EOGlobalID *)gid
|
|
after: (NSTimeInterval)ti
|
|
{
|
|
//OK
|
|
NSDictionary *snapshot = nil;
|
|
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"self=%p database=%p",
|
|
self, _database);
|
|
|
|
|
|
snapshot = [self localSnapshotForGlobalID: gid];
|
|
|
|
if (!snapshot)
|
|
{
|
|
NSAssert(_database, @"No database");
|
|
snapshot = [_database snapshotForGlobalID: gid
|
|
after: ti];
|
|
}
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"snapshot for gid %@: %p %@",
|
|
gid, snapshot, snapshot);
|
|
|
|
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
- (void)recordSnapshot: (NSArray *)gids
|
|
forSourceGlobalID: (EOGlobalID *)gid
|
|
relationshipName: (NSString *)name
|
|
{
|
|
|
|
|
|
NSEmitTODO();
|
|
|
|
[self notImplemented: _cmd]; //TODO
|
|
/*
|
|
NSMutableDictionary *toMany = [_toManySnapshots objectForKey:gid];
|
|
|
|
if (toMany == nil)
|
|
{
|
|
toMany = [NSMutableDictionary dictionaryWithCapacity:16];
|
|
[_toManySnapshots setObject:toMany
|
|
forKey:gid];
|
|
}
|
|
|
|
[toMany setObject:gids
|
|
forKey:name];
|
|
*/
|
|
|
|
|
|
}
|
|
|
|
- (NSArray *)snapshotForSourceGlobalID: (EOGlobalID *)gid
|
|
relationshipName: (NSString *)name
|
|
{
|
|
NSArray *snapshot = nil;
|
|
|
|
|
|
NSEmitTODO();
|
|
|
|
[self notImplemented: _cmd]; //TODO
|
|
/*
|
|
|
|
snapshot = [[_toManySnapshots objectForKey:gid] objectForKey:name];
|
|
if (!snapshot)
|
|
snapshot=[_database snapshotForSourceGlobalID:gid
|
|
relationshipName:name];
|
|
*/
|
|
|
|
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
- (NSDictionary *)localSnapshotForGlobalID: (EOGlobalID *)gid
|
|
{
|
|
//OK
|
|
NSDictionary *snapshot = nil;
|
|
int snapshotsDictCount = 0;
|
|
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"self=%p database=%p",
|
|
self, _database);
|
|
|
|
snapshotsDictCount = [_uniqueStack count];
|
|
|
|
if (snapshotsDictCount>0)
|
|
{
|
|
int i = 0;
|
|
IMP oaiIMP=[_uniqueStack methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i = 0; !snapshot && i < snapshotsDictCount; i++)
|
|
{
|
|
NSDictionary *snapshots = GDL2_ObjectAtIndexWithImp(_uniqueStack,oaiIMP,i);
|
|
snapshot = [snapshots objectForKey: gid];
|
|
}
|
|
};
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"snapshot for gid %@: %p %@",
|
|
gid, snapshot, snapshot);
|
|
|
|
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
- (NSArray *)localSnapshotForSourceGlobalID: (EOGlobalID *)gid
|
|
relationshipName: (NSString *)name
|
|
{
|
|
NSArray *snapshot = nil;
|
|
|
|
//TODO
|
|
|
|
NSEmitTODO();
|
|
|
|
[self notImplemented: _cmd]; //TODO
|
|
/*
|
|
return [[_toManySnapshots objectForKey:gid] objectForKey:name];
|
|
*/
|
|
|
|
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
- (void)forgetSnapshotForGlobalID: (EOGlobalID *)gid
|
|
{
|
|
//TODO-VERIFY deleteStack
|
|
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"self=%p database=%p [_uniqueStack count]=%d",
|
|
self, _database,[_uniqueStack count]);
|
|
|
|
if ([_uniqueStack count] > 0)
|
|
{
|
|
NSMutableDictionary *uniqueSS = [_uniqueStack lastObject];
|
|
NSMutableDictionary *uniqArSS = [_uniqueArrayStack lastObject];
|
|
NSMutableSet *deleteSS = [_deleteStack lastObject];
|
|
|
|
[deleteSS addObject: gid];
|
|
[uniqueSS removeObjectForKey: gid];
|
|
[uniqArSS removeObjectForKey: gid];
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (void)forgetSnapshotsForGlobalIDs: (NSArray *)gids
|
|
{
|
|
unsigned i, j, n, m;
|
|
NSMutableDictionary *snapshots;
|
|
NSMutableSet *deleteGIDs;
|
|
EOGlobalID *gid;
|
|
|
|
|
|
n = [_uniqueStack count];
|
|
if (n>0)
|
|
{
|
|
IMP oaiIMP=[_uniqueStack methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i=0; i<n; i++)
|
|
{
|
|
snapshots = GDL2_ObjectAtIndexWithImp(_uniqueStack,oaiIMP,i);
|
|
[snapshots removeObjectsForKeys: gids];
|
|
}
|
|
};
|
|
|
|
n = [_uniqueArrayStack count];
|
|
if (n>0)
|
|
{
|
|
IMP oaiIMP
|
|
= [_uniqueArrayStack methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i=0; i<n; i++)
|
|
{
|
|
snapshots = GDL2_ObjectAtIndexWithImp(_uniqueArrayStack,oaiIMP,i);
|
|
[snapshots removeObjectsForKeys: gids];
|
|
}
|
|
};
|
|
|
|
n = [_deleteStack count];
|
|
if (n>0)
|
|
{
|
|
IMP oaiIMP=[_deleteStack methodForSelector: @selector(objectAtIndex:)];
|
|
IMP oaiIMP2=[gids methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
m = [gids count];
|
|
for (i=0; i<n; i++)
|
|
{
|
|
deleteGIDs = GDL2_ObjectAtIndexWithImp(_deleteStack,oaiIMP,i);
|
|
for (j=0; j<m; j++)
|
|
{
|
|
gid = GDL2_ObjectAtIndexWithImp(gids, oaiIMP2, j);
|
|
[deleteGIDs removeObject: gid];
|
|
}
|
|
}
|
|
}
|
|
|
|
[_database forgetSnapshotsForGlobalIDs: gids];
|
|
|
|
}
|
|
|
|
- (void)recordSnapshots: (NSDictionary *)snapshots
|
|
{
|
|
|
|
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd]; //TODO
|
|
/*
|
|
[_snapshots addEntriesFromDictionary:snapshots];
|
|
*/
|
|
|
|
|
|
}
|
|
|
|
- (void)recordToManySnapshots: (NSDictionary *)snapshots
|
|
{
|
|
|
|
//OK
|
|
|
|
if ([_uniqueArrayStack count] > 0)
|
|
{
|
|
NSMutableDictionary *toManySnapshots = [_uniqueArrayStack lastObject];
|
|
NSArray *keys = [snapshots allKeys];
|
|
int count = [keys count];
|
|
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[keys methodForSelector: @selector(objectAtIndex:)];
|
|
int i = 0;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
id key = GDL2_ObjectAtIndexWithImp(keys,oaiIMP,i);
|
|
NSDictionary *snapshotsDict = [snapshots objectForKey: key];
|
|
NSMutableDictionary *currentSnapshotsDict =
|
|
[toManySnapshots objectForKey: key];
|
|
|
|
if (!currentSnapshotsDict)
|
|
{
|
|
currentSnapshotsDict = (NSMutableDictionary *)[NSMutableDictionary
|
|
dictionary];
|
|
[toManySnapshots setObject: currentSnapshotsDict
|
|
forKey: key];
|
|
}
|
|
|
|
[currentSnapshotsDict addEntriesFromDictionary: snapshotsDict];
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (void)registerLockedObjectWithGlobalID: (EOGlobalID *)globalID
|
|
{
|
|
|
|
|
|
if (!_lockedObjects)
|
|
{
|
|
_lockedObjects
|
|
= NSCreateHashTable(NSNonOwnedPointerHashCallBacks, _LOCK_BUFFER);
|
|
}
|
|
|
|
NSHashInsert(_lockedObjects, globalID);
|
|
|
|
|
|
}
|
|
|
|
- (BOOL)isObjectLockedWithGlobalID: (EOGlobalID *)globalID
|
|
{
|
|
BOOL result;
|
|
|
|
|
|
|
|
result = (_lockedObjects && NSHashGet(_lockedObjects, globalID) != nil);
|
|
|
|
|
|
|
|
return result;
|
|
}
|
|
|
|
- (void)initializeObject: (id)object
|
|
row: (NSDictionary*)row
|
|
entity: (EOEntity*)entity
|
|
editingContext: (EOEditingContext*)context
|
|
{
|
|
//really near ok
|
|
NSArray *relationships = nil;
|
|
NSArray *classPropertyAttributeNames = nil;
|
|
NSUInteger count = 0;
|
|
IMP rowObjectForKeyIMP=NULL;
|
|
|
|
classPropertyAttributeNames = [entity classPropertyAttributeNames];
|
|
count = [classPropertyAttributeNames count];
|
|
|
|
//row is usuallly a EOMutableKnownKeyDictionary so will use EOMKKD_objectForKeyWithImpPtr
|
|
|
|
if (count>0)
|
|
{
|
|
NSUInteger i=0;
|
|
IMP oaiIMP=[classPropertyAttributeNames methodForSelector:@selector(objectAtIndex:)];
|
|
|
|
NSAssert(!_isFault(object),
|
|
@"Object is a fault. call -methodForSelector: on it is a bad idea");
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
id key = GDL2_ObjectAtIndexWithImp(classPropertyAttributeNames,oaiIMP,i);
|
|
id value = nil;
|
|
|
|
|
|
value = EOMKKD_objectForKeyWithImpPtr(row,&rowObjectForKeyIMP,key);
|
|
|
|
if (value == GDL2_EONull)
|
|
value = nil;
|
|
|
|
[object takeStoredValue:value
|
|
forKey:key];
|
|
}
|
|
};
|
|
|
|
relationships = [entity _relationshipsToFaultForRow: row];
|
|
|
|
|
|
|
|
count = [relationships count];
|
|
|
|
if (count>0)
|
|
{
|
|
NSUInteger i=0;
|
|
IMP oaiIMP=[relationships methodForSelector:@selector(objectAtIndex:)];
|
|
|
|
NSAssert(!_isFault(object),
|
|
@"Object is a fault. call -methodForSelector: on it is a bad idea");
|
|
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
id relObject = nil;
|
|
EORelationship *relationship = GDL2_ObjectAtIndexWithImp(relationships,oaiIMP,i);
|
|
NSString *relName = [relationship name];
|
|
|
|
|
|
if ([relationship isToMany])
|
|
{
|
|
EOGlobalID *gid = [entity globalIDForRow: row];
|
|
|
|
relObject = [self arrayFaultWithSourceGlobalID: gid
|
|
relationshipName: relName
|
|
editingContext: context];
|
|
}
|
|
else if ([relationship isFlattened])
|
|
{
|
|
// to one flattened relationship like aRelationship.anotherRelationship...
|
|
|
|
// I don't know how to handle this case.... May be we shouldn't treat this as real property ??
|
|
NSEmitTODO();
|
|
relObject = nil;
|
|
}
|
|
else
|
|
{
|
|
EOMutableKnownKeyDictionary *foreignKeyForSourceRow = nil;
|
|
|
|
foreignKeyForSourceRow = [relationship _foreignKeyForSourceRow: row];
|
|
|
|
if (![foreignKeyForSourceRow
|
|
containsObjectsNotIdenticalTo: GDL2_EONull])
|
|
{
|
|
NSEmitTODO();//TODO: what to do if rel is mandatory ?
|
|
relObject = nil;
|
|
}
|
|
else
|
|
{
|
|
EOEntity *destinationEntity = [relationship destinationEntity];
|
|
EOGlobalID *relRowGid = [destinationEntity
|
|
globalIDForRow: foreignKeyForSourceRow];
|
|
|
|
|
|
|
|
if ([(EOKeyGlobalID*)relRowGid areKeysAllNulls])
|
|
NSWarnLog(@"All key of relRowGid %p (%@) are nulls",
|
|
relRowGid,
|
|
relRowGid);
|
|
|
|
relObject = [context faultForGlobalID: relRowGid
|
|
editingContext: context];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"relObject=%p (%@)",
|
|
relObject, [relObject class]);
|
|
//end
|
|
/*
|
|
NSArray *joins = [(EORelationship *)prop joins];
|
|
EOJoin *join;
|
|
NSMutableDictionary *row;
|
|
EOGlobalID *faultGID;
|
|
int h, count;
|
|
id value, realValue = nil;
|
|
|
|
row = [NSMutableDictionary dictionaryWithCapacity:4];
|
|
|
|
count = [joins count];
|
|
for (h=0; h<count; h++)
|
|
{
|
|
join = [joins objectAtIndex:h];
|
|
|
|
value = [snapshot objectForKey:[[join sourceAttribute]
|
|
name]];
|
|
if (value == null)
|
|
realValue = nil;
|
|
else
|
|
realValue = value;
|
|
|
|
[[prop validateValue:&realValue] raise];
|
|
|
|
[row setObject:value
|
|
forKey:[[join destinationAttribute]
|
|
name]];
|
|
}
|
|
|
|
if (realValue || [prop isMandatory] == YES)
|
|
{
|
|
faultGID = [[(EORelationship *)prop destinationEntity]
|
|
globalIDForRow:row];
|
|
|
|
fault = [context objectForGlobalID:faultGID];
|
|
|
|
if (fault == nil)
|
|
fault = [context faultForGlobalID:faultGID
|
|
editingContext:context];
|
|
}
|
|
else
|
|
fault = nil;
|
|
|
|
*/
|
|
}
|
|
}
|
|
|
|
[object takeStoredValue:relObject
|
|
forKey:relName];
|
|
}
|
|
};
|
|
|
|
|
|
}
|
|
|
|
- (void)forgetAllLocks
|
|
{
|
|
if (_lockedObjects)
|
|
{
|
|
NSResetHashTable(_lockedObjects);
|
|
}
|
|
}
|
|
|
|
- (void)forgetLocksForObjectsWithGlobalIDs: (NSArray *)gids
|
|
{
|
|
if (_lockedObjects)
|
|
{
|
|
unsigned n;
|
|
EOGlobalID *gid;
|
|
|
|
n = [gids count];
|
|
|
|
if (n>0)
|
|
{
|
|
IMP oaiIMP=[gids methodForSelector: @selector(objectAtIndex:)];
|
|
unsigned i = 0;
|
|
|
|
for (i=0; i<n; i++)
|
|
{
|
|
gid = GDL2_ObjectAtIndexWithImp(gids,oaiIMP,i);
|
|
NSHashRemove(_lockedObjects, gid);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
- (void)_rollbackTransaction
|
|
{
|
|
|
|
|
|
if ([_uniqueStack count] > 0)
|
|
{
|
|
[self forgetAllLocks];
|
|
|
|
[_uniqueStack removeLastObject];
|
|
[_uniqueArrayStack removeLastObject];
|
|
[_deleteStack removeLastObject];
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (void)_commitTransaction
|
|
{
|
|
|
|
if ([_uniqueStack count] > 0)
|
|
{
|
|
NSMutableDictionary *snapshotsDict = [_uniqueStack lastObject];
|
|
NSMutableDictionary *toManySnapshotsDict = [_uniqueArrayStack lastObject];
|
|
NSMutableSet *deleteSnapshotsSet = [_deleteStack lastObject];
|
|
NSEnumerator *deletedGIDEnum = [deleteSnapshotsSet objectEnumerator];
|
|
EOGlobalID *gid;
|
|
|
|
while ((gid = [deletedGIDEnum nextObject]))
|
|
{
|
|
[_database forgetSnapshotForGlobalID: gid];
|
|
}
|
|
|
|
[_database recordSnapshots: snapshotsDict];
|
|
[_database recordToManySnapshots: toManySnapshotsDict];
|
|
|
|
[self forgetAllLocks];
|
|
|
|
[_uniqueStack removeLastObject];
|
|
[_uniqueArrayStack removeLastObject];
|
|
[_deleteStack removeLastObject];
|
|
}
|
|
|
|
}
|
|
|
|
- (void) _beginTransaction
|
|
{
|
|
[_uniqueStack addObject: [NSMutableDictionary dictionary]];
|
|
[_uniqueArrayStack addObject: [NSMutableDictionary dictionary]];
|
|
[_deleteStack addObject: [NSMutableSet set]];
|
|
}
|
|
|
|
- (EODatabaseChannel*) _obtainOpenChannel
|
|
{
|
|
EODatabaseChannel *channel = [self availableChannel];
|
|
|
|
if (![self _openChannelWithLoginPanel: channel])
|
|
{
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd];//TODO
|
|
}
|
|
|
|
return channel;
|
|
}
|
|
|
|
- (BOOL) _openChannelWithLoginPanel: (EODatabaseChannel*)databaseChannel
|
|
{
|
|
// veridy: LoginPanel ???
|
|
EOAdaptorChannel *adaptorChannel = [databaseChannel adaptorChannel];
|
|
|
|
if (![adaptorChannel isOpen]) //??
|
|
{
|
|
[adaptorChannel openChannel];
|
|
}
|
|
|
|
return [adaptorChannel isOpen];
|
|
}
|
|
|
|
- (void) _forceDisconnect
|
|
{ // TODO
|
|
NSEmitTODO();
|
|
[self notImplemented: _cmd];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation EODatabaseContext(EOMultiThreaded)
|
|
|
|
- (void)lock
|
|
{
|
|
[_lock lock];
|
|
}
|
|
|
|
- (void)unlock
|
|
{
|
|
[_lock unlock];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation EODatabaseContext (EODatabaseContextPrivate2)
|
|
|
|
- (void) _verifyNoChangesToReadonlyEntity: (EODatabaseOperation*)dbOpe
|
|
{
|
|
//TODO
|
|
EOEntity *entity = nil;
|
|
|
|
|
|
|
|
entity = [dbOpe entity];
|
|
|
|
|
|
|
|
if ([entity isReadOnly])
|
|
{
|
|
//?? exception I presume
|
|
}
|
|
else
|
|
{
|
|
[dbOpe databaseOperator]; //SoWhat
|
|
}
|
|
|
|
|
|
}
|
|
|
|
- (void) _cleanUpAfterSave
|
|
{
|
|
// TODO -- dw
|
|
//EODatabase * eodatabase = [self database];
|
|
|
|
_coordinator = nil; //realesae ?
|
|
_editingContext = nil; //realesae ?
|
|
|
|
if (_dbOperationsByGlobalID)
|
|
{
|
|
//Really free it because we don't want to record some db ope (select for exemple).
|
|
NSFreeMapTable(_dbOperationsByGlobalID);
|
|
_dbOperationsByGlobalID = NULL;
|
|
}
|
|
|
|
_flags.beganTransaction = NO;
|
|
_flags.willPrepareForSave = NO;
|
|
_flags.preparingForSave = NO;
|
|
|
|
// TODO -- dw
|
|
// if (eodatabase != nil)
|
|
// {
|
|
// [eodatabase _clearLastRecords];
|
|
// }
|
|
|
|
}
|
|
|
|
- (EOGlobalID*)_globalIDForObject: (id)object
|
|
{
|
|
EOEditingContext *objectEditingContext = nil;
|
|
EOGlobalID *gid = nil;
|
|
|
|
|
|
NSAssert(object, @"No object");
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",@"object=%p of class %@",
|
|
object,[object class]);
|
|
NSDebugMLLog(@"EODatabaseContext", @"_editingContext=%p",
|
|
_editingContext);
|
|
|
|
objectEditingContext = [object editingContext];
|
|
NSAssert2(objectEditingContext, @"No editing context for object %p: %@",
|
|
object,object);
|
|
|
|
gid=EOEditingContext_globalIDForObjectWithImpPtr(objectEditingContext,
|
|
NULL,
|
|
object);
|
|
|
|
|
|
if (!gid)
|
|
{
|
|
NSEmitTODO();
|
|
NSLog(@"TODO: no GID in EODatabaseContext _globalIDForObject:");
|
|
//TODO exception ? ==> RollbackCh
|
|
}
|
|
|
|
|
|
|
|
return gid;
|
|
}
|
|
|
|
- (NSDictionary*)_primaryKeyForObject: (id)object
|
|
{
|
|
//Ayers: Review
|
|
return [self _primaryKeyForObject: object
|
|
raiseException: YES];
|
|
}
|
|
|
|
- (NSDictionary*)_primaryKeyForObject: (id)object
|
|
raiseException: (BOOL)raiseException
|
|
{
|
|
NSDictionary *pk = nil;
|
|
EOEntity *entity = nil;
|
|
NSArray *pkNames = nil;
|
|
NSDictionary *pk2 = nil;
|
|
|
|
NSAssert(!_isNilOrEONull(object), @"No object");
|
|
|
|
entity = [_database entityForObject: object];
|
|
|
|
EOGlobalID *gid = EODatabaseContext_globalIDForObjectWithImpPtr(self,NULL,object);
|
|
|
|
pk = [entity primaryKeyForGlobalID: (EOKeyGlobalID*)gid]; //OK
|
|
|
|
pkNames = [entity primaryKeyAttributeNames];
|
|
|
|
pk2 = [self valuesForKeys: pkNames
|
|
object: object];
|
|
|
|
if ([pk2 count] > 0)
|
|
{
|
|
if (pk)
|
|
{
|
|
//merge pk2 into pk
|
|
NSEnumerator *pk2Enum = [pk2 keyEnumerator];
|
|
IMP pk2EnumNO=NULL; // nextObject
|
|
NSMutableDictionary *realPK;
|
|
|
|
realPK = [NSMutableDictionary dictionaryWithDictionary: pk];
|
|
id key = nil;
|
|
|
|
while ((key = GDL2_NextObjectWithImpPtr(pk2Enum,&pk2EnumNO)))
|
|
{
|
|
id value = [pk2 objectForKey: key];
|
|
|
|
if (((value) && (value != GDL2_EONull)) &&
|
|
(([value isKindOfClass:[NSNumber class]] == NO) || ([value intValue] != 0)))
|
|
{
|
|
[realPK setObject: value
|
|
forKey: key];
|
|
}
|
|
}
|
|
|
|
pk = realPK;
|
|
}
|
|
else
|
|
pk=pk2;
|
|
}
|
|
|
|
if (([entity isPrimaryKeyValidInObject: pk] == NO)) {
|
|
pk = nil;
|
|
}
|
|
|
|
// no PK? Ask the delegate to make one for us.
|
|
if (pk == nil)
|
|
{
|
|
if (_delegateRespondsTo.newPrimaryKey == YES)
|
|
pk = [_delegate databaseContext: self
|
|
newPrimaryKeyForObject: object
|
|
entity: entity];
|
|
}
|
|
|
|
// still no PK?
|
|
if (pk == nil)
|
|
{
|
|
EOAdaptorChannel *channel = nil;
|
|
EOStoredProcedure *nextPKProcedure = nil;
|
|
|
|
nextPKProcedure = [entity storedProcedureForOperation:
|
|
EONextPrimaryKeyProcedureOperation];
|
|
|
|
if (nextPKProcedure)
|
|
{
|
|
NS_DURING {
|
|
|
|
channel = [[self _obtainOpenChannel] adaptorChannel];
|
|
|
|
[channel executeStoredProcedure:nextPKProcedure
|
|
withValues:nil];
|
|
|
|
pk = [channel returnValuesForLastStoredProcedureInvocation];
|
|
|
|
} NS_HANDLER {
|
|
// if the delegate took care about the exception
|
|
// or we lost connection, try it again.
|
|
if (([self _delegateHandledDatabaseException:localException]) ||
|
|
([[_database adaptor] isDroppedConnectionException:localException]))
|
|
{
|
|
channel = [[self _obtainOpenChannel] adaptorChannel];
|
|
|
|
[channel executeStoredProcedure:nextPKProcedure
|
|
withValues:nil];
|
|
|
|
pk = [channel returnValuesForLastStoredProcedureInvocation];
|
|
} else {
|
|
[localException raise];
|
|
}
|
|
}
|
|
} NS_ENDHANDLER;
|
|
}
|
|
|
|
if (pk)
|
|
{
|
|
pk = [entity primaryKeyForRow:pk];
|
|
}
|
|
|
|
if (!pk) {
|
|
EOAttribute * pkAttr = nil;
|
|
NSArray * pkAttributes = [entity primaryKeyAttributes];
|
|
|
|
if ((pkAttributes) && ([pkAttributes count] == 1)) {
|
|
pkAttr = [pkAttributes objectAtIndex: 0];
|
|
}
|
|
if ((pkAttr) && (([pkAttr adaptorValueType] == EOAdaptorBytesType) &&
|
|
([pkAttr width] == 24)))
|
|
{
|
|
unsigned char bytes[24];
|
|
id byteValue = nil;
|
|
|
|
memset(&bytes, 0, sizeof(bytes));
|
|
|
|
[EOTemporaryGlobalID assignGloballyUniqueBytes:&bytes[0]];
|
|
|
|
byteValue = [pkAttr newValueForBytes: &bytes
|
|
length:sizeof(bytes)];
|
|
pk = [NSDictionary dictionaryWithObject:byteValue
|
|
forKey:[pkAttr name]];
|
|
}
|
|
}
|
|
|
|
if (!pk) {
|
|
EOAdaptorChannel *channel = nil;
|
|
|
|
NS_DURING {
|
|
channel = [[self _obtainOpenChannel] adaptorChannel];
|
|
|
|
pk = [channel primaryKeyForNewRowWithEntity:entity];
|
|
|
|
} NS_HANDLER {
|
|
// if the delegate took care about the exception
|
|
// or we lost connection, try it again.
|
|
if (([self _delegateHandledDatabaseException:localException]) ||
|
|
([[_database adaptor] isDroppedConnectionException:localException]))
|
|
{
|
|
channel = [[self _obtainOpenChannel] adaptorChannel];
|
|
|
|
pk = [channel primaryKeyForNewRowWithEntity:entity];
|
|
|
|
} else {
|
|
[localException raise];
|
|
}
|
|
} NS_ENDHANDLER;
|
|
}
|
|
// TODO: The reference does not raise here I suppose -- dw.
|
|
// if (!pk) {
|
|
// [NSException raise: NSInvalidArgumentException
|
|
// format: @"%@ -- %@ 0x%x: cannot generate primary key for object '%@'",
|
|
// NSStringFromSelector(_cmd),
|
|
// NSStringFromClass([self class]),
|
|
// self, object];
|
|
// }
|
|
|
|
|
|
return pk;
|
|
}
|
|
|
|
- (BOOL) _shouldGeneratePrimaryKeyForEntityName: (NSString*)entityName
|
|
{
|
|
//OK
|
|
BOOL shouldGeneratePK = YES;
|
|
|
|
|
|
|
|
if (_nonPrimaryKeyGenerators)
|
|
shouldGeneratePK = !NSHashGet(_nonPrimaryKeyGenerators, entityName);
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"shouldGeneratePK for %@: %s",
|
|
entityName,
|
|
(shouldGeneratePK ? "YES" : "NO"));
|
|
NSAssert(![entityName isEqualToString: @"Country"]
|
|
|| shouldGeneratePK, @"MGVALID: Failed");
|
|
return shouldGeneratePK;
|
|
}
|
|
|
|
// private
|
|
- (EOEntity*) _entityForObject:(EOCustomObject*) eo
|
|
{
|
|
EOEntity * entity = nil;
|
|
EOObjectStoreCoordinator * coordinator = nil;
|
|
NSArray * cooperatingObjectStores = nil;
|
|
NSUInteger i = 0;
|
|
NSUInteger count = 0;
|
|
|
|
if (!eo)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"%s -- Enterprise object cannot be nil", __PRETTY_FUNCTION__];
|
|
}
|
|
|
|
entity = [_database entityForObject:eo];
|
|
|
|
if (entity)
|
|
{
|
|
return entity;
|
|
}
|
|
|
|
coordinator = [self coordinator];
|
|
if (!coordinator)
|
|
{
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"%s -- Object store coordinator cannot be nil", __PRETTY_FUNCTION__];
|
|
}
|
|
|
|
cooperatingObjectStores = [coordinator cooperatingObjectStores];
|
|
|
|
count = [cooperatingObjectStores count];
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
id store = [cooperatingObjectStores objectAtIndex:i];
|
|
if (!([store isKindOfClass:[EODatabaseContext class]]))
|
|
{
|
|
continue;
|
|
}
|
|
entity = [[(EODatabaseContext*)store database] entityForObject:eo];
|
|
if (entity)
|
|
{
|
|
return entity;
|
|
}
|
|
}
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"%s -- Unable to find entity for object %@", __PRETTY_FUNCTION__, eo];
|
|
// no warnings pls.
|
|
return nil;
|
|
}
|
|
|
|
- (void)_buildPrimaryKeyGeneratorListForEditingContext: (EOEditingContext*)context
|
|
{
|
|
NSArray *objects[3];
|
|
NSHashTable *processedEntities = NULL;
|
|
NSMutableArray *entityToProcess = nil;
|
|
NSUInteger which;
|
|
|
|
|
|
|
|
if (_nonPrimaryKeyGenerators)
|
|
{
|
|
NSResetHashTable(_nonPrimaryKeyGenerators);
|
|
}
|
|
|
|
processedEntities = NSCreateHashTable(NSObjectHashCallBacks, 32);
|
|
|
|
objects[0] = [context updatedObjects];
|
|
objects[1] = [context insertedObjects];
|
|
objects[2] = [context deletedObjects];
|
|
|
|
for (which = 0; which < 3; which++)
|
|
{
|
|
NSUInteger count = [objects[which] count];
|
|
|
|
if (count>0)
|
|
{
|
|
IMP oaiIMP=[objects[which] methodForSelector: @selector(objectAtIndex:)];
|
|
NSUInteger i = 0;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
id object = GDL2_ObjectAtIndexWithImp(objects[which],oaiIMP,i);
|
|
EOEntity *entity = [_database entityForObject: object];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"add entity to process: %@", [entity name]);
|
|
|
|
if (entityToProcess)
|
|
[entityToProcess addObject: entity];
|
|
else
|
|
entityToProcess = [NSMutableArray arrayWithObject: entity];
|
|
}
|
|
};
|
|
}
|
|
|
|
while ([entityToProcess count])
|
|
{
|
|
EOEntity *entity = [entityToProcess lastObject];
|
|
|
|
|
|
|
|
[entityToProcess removeLastObject];
|
|
|
|
if (!NSHashInsertIfAbsent(processedEntities, entity)) //Already processed ?
|
|
{
|
|
NSArray *relationships = [entity relationships];
|
|
NSUInteger relationshipsCount = [relationships count];
|
|
|
|
if (relationshipsCount>0)
|
|
{
|
|
IMP relObjectAtIndexIMP=[relationships methodForSelector: @selector(objectAtIndex:)];
|
|
NSUInteger iRelationship = 0;
|
|
|
|
for (iRelationship = 0;
|
|
iRelationship < relationshipsCount;
|
|
iRelationship++)
|
|
{
|
|
EORelationship *relationship = GDL2_ObjectAtIndexWithImp(relationships,relObjectAtIndexIMP,iRelationship);
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"test entity: %@ relationship=%@",
|
|
[entity name],
|
|
relationship);
|
|
|
|
if ([relationship propagatesPrimaryKey])
|
|
{
|
|
EOEntity *destinationEntity = [relationship
|
|
destinationEntity];
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"test entity: %@ destinationEntity=%@",
|
|
[entity name],
|
|
[destinationEntity name]);
|
|
|
|
if (destinationEntity)
|
|
{
|
|
NSArray *destAttrs;
|
|
NSArray *pkAttrs;
|
|
NSUInteger count;
|
|
BOOL destPK = NO;
|
|
|
|
destAttrs = [relationship destinationAttributes];
|
|
pkAttrs = [destinationEntity primaryKeyAttributes];
|
|
count = [destAttrs count];
|
|
|
|
if (count>0)
|
|
{
|
|
IMP destAttrsObjectAtIndexIMP=[relationships methodForSelector: @selector(objectAtIndex:)];
|
|
NSUInteger i=0;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if ([pkAttrs containsObject:
|
|
GDL2_ObjectAtIndexWithImp(destAttrs,destAttrsObjectAtIndexIMP,i)])
|
|
destPK = YES;
|
|
}
|
|
};
|
|
if (destPK)
|
|
{
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"destination entity: %@ "
|
|
@"No PK generation [Rel = %@]",
|
|
[destinationEntity name],
|
|
[relationship name]);
|
|
|
|
if (!_nonPrimaryKeyGenerators)
|
|
_nonPrimaryKeyGenerators = NSCreateHashTable(NSObjectHashCallBacks, 32);
|
|
|
|
NSHashInsertIfAbsent(_nonPrimaryKeyGenerators, [destinationEntity name]);
|
|
[entityToProcess addObject: destinationEntity];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"_nonPrimaryKeyGenerators=%@",
|
|
NSStringFromHashTable(_nonPrimaryKeyGenerators));
|
|
|
|
|
|
|
|
NSFreeHashTable(processedEntities);
|
|
}
|
|
|
|
/** Returns a dictionary containing a snapshot of object that reflects its committed values (last values putted in the database; i.e. values before changes were made on the object).
|
|
It is updated after commiting new values.
|
|
If the object has been just inserted, the dictionary is empty.
|
|
**/
|
|
- (NSDictionary*)_currentCommittedSnapshotForObject: (id)object
|
|
{
|
|
NSDictionary *snapshot = nil;
|
|
EOGlobalID *gid = nil;
|
|
EODatabaseOperation *dbOpe = nil;
|
|
EODatabaseOperator dbOperator = (EODatabaseOperator)0;
|
|
|
|
|
|
|
|
gid = EOEditingContext_globalIDForObjectWithImpPtr(_editingContext,NULL,object);
|
|
dbOpe = [self databaseOperationForGlobalID: gid]; //I'm not sure. Retrieve it directly ?
|
|
dbOperator = [dbOpe databaseOperator];
|
|
|
|
switch (dbOperator)
|
|
{
|
|
case EODatabaseUpdateOperator:
|
|
snapshot = [_editingContext committedSnapshotForObject: object];//OK
|
|
break;
|
|
|
|
case EODatabaseInsertOperator:
|
|
snapshot = [NSDictionary dictionary];
|
|
break;
|
|
|
|
//TODO
|
|
/* else
|
|
snapshot=XX;//TODO
|
|
*/
|
|
case EODatabaseDeleteOperator:
|
|
break;
|
|
|
|
case EODatabaseNothingOperator:
|
|
break;
|
|
}
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
- (void) _assertValidStateWithSelector: (SEL)sel
|
|
{
|
|
if ((!_flags.preparingForSave) && (!_flags.willPrepareForSave))
|
|
{
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"_assertValidStateWithSelector:%s %s is in invalid state, "
|
|
"call prepareForSaveWithCoordinator: before calling this method.",
|
|
sel_getName(sel),
|
|
object_getClassName(self)];
|
|
|
|
}
|
|
}
|
|
|
|
- (id) _addDatabaseContextStateToException: (id)param0
|
|
{
|
|
NSEmitTODO();
|
|
return [self notImplemented: _cmd]; //TODO
|
|
}
|
|
|
|
- (id) _databaseContextState
|
|
{
|
|
NSEmitTODO();
|
|
return [self notImplemented: _cmd]; //TODO
|
|
}
|
|
|
|
@end
|