mirror of
https://github.com/gnustep/libs-gdl2.git
synced 2025-02-21 02:20:55 +00:00
respect GNUstep coding standard (curly brackets placement, indentation) use IMP for some -objectAtIndex calls * EOControl/EONSAddOns.h add -overrideEntriesWithObjectsFromDictionary:forKeys: add +diffOldArray:newArray:returnsRemovedValues:addedValues: * EOControl/EONSAddOns.h add -overrideEntriesWithObjectsFromDictionary:forKeys: add +diffOldArray:newArray:returnsRemovedValues:addedValues: use IMP for some -objectAtIndex calls use NSUInteger for count and indexes rewrite -containsIdenticalObjectsWithArray: to use diffOldArray:... * EOControl/EODetailDataSource.m fix -editingContext * EOAccess/EODatabaseContext.m use IMP for some -objectAtIndex calls rewrite -faultForGlobalID:editingContext: optimize -recordChangesInEditingContext: rewrite part of -databaseOperationForObject: git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gdl2/trunk@37920 72102866-910b-0410-8b05-ffd578937521
6190 lines
175 KiB
Objective-C
6190 lines
175 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=%"PRIuPTR,
|
|
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=%"PRIuPTR,
|
|
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=NULL;
|
|
|
|
for (i = 0 ; !busy && i < count; i++)
|
|
{
|
|
EODatabaseChannel *channel = GDL2_ObjectAtIndexWithImpPtr(_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];
|
|
|
|
//Channels have same delegate as context
|
|
[channel setDelegate: _delegate];
|
|
}
|
|
|
|
- (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%p: 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%p: 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' **/
|
|
//MG2014: OK
|
|
- (id) entityForGlobalID: (EOGlobalID *)globalID
|
|
{
|
|
NSString *entityName=[(EOKeyGlobalID *)globalID entityName];
|
|
|
|
if (_lastEntity==nil
|
|
|| ![[_lastEntity name] isEqualToString:entityName])
|
|
{
|
|
ASSIGN(_lastEntity, [_database entityNamed: entityName]);
|
|
}
|
|
return _lastEntity;
|
|
}
|
|
|
|
/** Make object a fault **/
|
|
- (void) _turnToFault: (id)object
|
|
gid: (EOGlobalID *)globalID
|
|
editingContext: (EOEditingContext *)context
|
|
isComplete: (BOOL)isComplete
|
|
{
|
|
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' **/
|
|
//MG2014: OK
|
|
- (id)faultForGlobalID: (EOGlobalID *)globalID
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
id object = nil;
|
|
|
|
if (![globalID isKindOfClass:[EOKeyGlobalID class]])
|
|
{
|
|
[NSException raise: @"NSIllegalStateException"
|
|
format:@"%s: The globalID %@ must be an EOKeyGlobalID to be able to construct a fault.",
|
|
__PRETTY_FUNCTION__,
|
|
globalID];
|
|
}
|
|
else
|
|
{
|
|
EOEntity* entity = [self entityForGlobalID: globalID];
|
|
NSAssert(entity, @"no entity");
|
|
|
|
if ([(EOKeyGlobalID *)globalID isFinal])
|
|
{
|
|
object = [[entity classDescriptionForInstances]
|
|
createInstanceWithEditingContext: context
|
|
globalID: globalID
|
|
zone: NULL];
|
|
[self _turnToFault: object
|
|
gid: globalID
|
|
editingContext: context
|
|
isComplete: YES];
|
|
}
|
|
else
|
|
{
|
|
object = [self _fetchSingleObjectForEntity:entity
|
|
globalID:globalID
|
|
editingContext:context];
|
|
}
|
|
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;
|
|
}
|
|
|
|
//MG2014: TODO: implement use of _currentGlobalID
|
|
- (void)initializeObject: (id)object
|
|
withGlobalID: (EOGlobalID *)globalID
|
|
editingContext: (EOEditingContext *)context
|
|
{
|
|
NSDictionary * snapshot = nil;
|
|
EOEntity * entity = nil;
|
|
/*if (globalID == _currentGlobalID)
|
|
{
|
|
snapshot = _currentSnapshot;
|
|
entity = _lastEntity;
|
|
}
|
|
else*/
|
|
{
|
|
if ([globalID isTemporary])
|
|
return;
|
|
else
|
|
{
|
|
snapshot = [self snapshotForGlobalID:globalID];
|
|
if ([(EOKeyGlobalID *)globalID isFinal])
|
|
{
|
|
entity = [self entityForGlobalID:globalID];
|
|
}
|
|
else
|
|
{
|
|
object = [context objectForGlobalID:globalID];
|
|
if (object==nil)
|
|
{
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"%s No object for gid %@ in %@",
|
|
__PRETTY_FUNCTION__, globalID, context];
|
|
}
|
|
entity = [_database entityForObject:object];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (snapshot==nil)
|
|
{
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"%s No snapshot for gid %@",
|
|
__PRETTY_FUNCTION__, globalID];
|
|
}
|
|
else
|
|
{
|
|
[self initializeObject: object
|
|
row: snapshot
|
|
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;
|
|
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];
|
|
|
|
if(maxBatch == 0)
|
|
{
|
|
if ([relationship isToManyToOne])
|
|
{
|
|
maxBatch = [[relationship firstRelationship]numberOfToManyFaultsToBatchFetch];
|
|
if(maxBatch == 0)
|
|
maxBatch=1;
|
|
}
|
|
}
|
|
//TODO: use maxBatch
|
|
|
|
//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)
|
|
|
|
//MG2014: TODO: implement use of _currentGlobalID
|
|
- (BOOL)ownsGlobalID: (EOGlobalID *)globalID
|
|
{
|
|
BOOL ownsGlobalID=NO;
|
|
/*if (_currentGlobalID == eoglobalid)
|
|
ownsGlobalID=YES;
|
|
else*/ if ([globalID isKindOfClass: [EOKeyGlobalID class]]
|
|
&& [_database entityNamed: [(EOKeyGlobalID*) globalID entityName]] != nil)
|
|
ownsGlobalID=YES;
|
|
|
|
return ownsGlobalID;
|
|
}
|
|
|
|
- (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* primaryKeys = nil;
|
|
|
|
NS_DURING
|
|
{
|
|
primaryKeys = [[[self _obtainOpenChannel] adaptorChannel] primaryKeysForNewRowsWithEntity:eoentity
|
|
count:i];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
if ([self _delegateHandledDatabaseException:localException])
|
|
{
|
|
primaryKeys = [[[self _obtainOpenChannel] adaptorChannel] primaryKeysForNewRowsWithEntity:eoentity
|
|
count:i];
|
|
}
|
|
else if ([[_database adaptor] isDroppedConnectionException:localException])
|
|
{
|
|
[_database handleDroppedConnection];
|
|
primaryKeys = [[[self _obtainOpenChannel] adaptorChannel] primaryKeysForNewRowsWithEntity:eoentity
|
|
count:i];
|
|
}
|
|
else
|
|
{
|
|
[localException raise];
|
|
}
|
|
}
|
|
NS_ENDHANDLER;
|
|
|
|
return primaryKeys;
|
|
}
|
|
|
|
// 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 != nil)
|
|
{
|
|
[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==nil
|
|
|| [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];
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//MG2014: OK
|
|
- (void)recordChangesInEditingContext
|
|
{
|
|
//_EOAssertSafeMultiThreadedAccess
|
|
|
|
[self _assertValidStateWithSelector:
|
|
@selector(recordChangesInEditingContext)];
|
|
|
|
|
|
NSArray * insertedObjects = [_editingContext insertedObjects];
|
|
NSArray * deletedObjects = [_editingContext deletedObjects];
|
|
NSArray * insertedAndUpdatedObjects = nil;
|
|
|
|
NSUInteger i = 0;
|
|
|
|
for (i = [deletedObjects count]; i > 0; i--)
|
|
{
|
|
EOCustomObject * eo = [deletedObjects objectAtIndex:i - 1];
|
|
EOEntity * eoentity = [_database entityForObject:eo];
|
|
if (eoentity!=nil)
|
|
{
|
|
NSArray * relationships = [eoentity relationships];
|
|
NSUInteger idx = 0;
|
|
|
|
for (idx = [relationships count]; idx > 0; idx--)
|
|
{
|
|
EORelationship * relationship = [relationships objectAtIndex:idx-1];
|
|
|
|
if ([relationship isToManyToOne])
|
|
{
|
|
NSDictionary* 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];
|
|
}
|
|
}
|
|
|
|
insertedAndUpdatedObjects = [[_editingContext updatedObjects] arrayByAddingObjectsFromArray:insertedObjects];
|
|
for (i = [insertedAndUpdatedObjects count]; i > 0; i--)
|
|
{
|
|
EOCustomObject * eo = [insertedAndUpdatedObjects objectAtIndex:i-1];
|
|
EOEntity * eoentity = [_database entityForObject:eo];
|
|
if (eoentity!=nil)
|
|
{
|
|
NSDictionary * updatedEOSnap = nil;
|
|
NSDictionary * currentCSnapDict = nil;
|
|
|
|
NSArray * relationships = nil;
|
|
NSDictionary * dbSnapshotDict = nil;
|
|
NSUInteger relCount = 0;
|
|
NSUInteger idx = 0;
|
|
|
|
[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 currentSnapValue = nil;
|
|
|
|
if (!dbSnapshotDict)
|
|
dbSnapshotDict = [[self databaseOperationForObject:eo] dbSnapshot];
|
|
|
|
relationship = [relationships objectAtIndex:idx];
|
|
substRelationship = [relationship _substitutionRelationshipForRow:dbSnapshotDict];
|
|
|
|
if (substRelationship)
|
|
{
|
|
classProperties = [eoentity classProperties];
|
|
|
|
if (([classProperties indexOfObject:substRelationship] != NSNotFound))
|
|
{
|
|
NSString* substRelationshipName=[substRelationship name];
|
|
id updatedSnapValue = [updatedEOSnap objectForKey:substRelationshipName];
|
|
|
|
if (updatedSnapValue == GDL2_EONull)
|
|
updatedSnapValue = nil;
|
|
|
|
currentSnapValue = [currentCSnapDict objectForKey:substRelationshipName];
|
|
if (currentSnapValue == GDL2_EONull)
|
|
currentSnapValue = nil;
|
|
|
|
if ([substRelationship isToMany])
|
|
{
|
|
NSArray* updatedSnapValues = (NSArray*) updatedSnapValue;
|
|
NSArray* currentSnapValues = (NSArray*) currentSnapValue;
|
|
NSArray * oldValues = nil;
|
|
|
|
//Here we use optimized method
|
|
//Think you may have more than 1000 objects in each of these arrays so imagine a 1000x1000 comparisons...
|
|
BOOL isDiff=[NSArray diffOldArray:currentSnapValues
|
|
newArray:updatedSnapValues
|
|
returnsRemovedValues:&oldValues
|
|
addedValues:NULL];
|
|
if (isDiff)
|
|
{
|
|
NSUInteger oldValuesCount=[oldValues count];
|
|
if (oldValuesCount>0)
|
|
{
|
|
if (_useToManyCaching)
|
|
{
|
|
IMP oldValues_oaiIMP=NULL;
|
|
IMP aoIMP=NULL;
|
|
EODatabaseOperation * updatedEoDbOp = [self databaseOperationForObject:eo];
|
|
NSUInteger valCount = [oldValues count];
|
|
NSMutableArray * globalIDs = [NSMutableArray arrayWithCapacity:valCount];
|
|
NSUInteger x = 0;
|
|
|
|
for (; x < valCount; x++)
|
|
{
|
|
EOCustomObject * updatedEo = GDL2_ObjectAtIndexWithImpPtr(oldValues,&oldValues_oaiIMP,x);
|
|
EOGlobalID * globalID = [_editingContext globalIDForObject:updatedEo];
|
|
|
|
if (!globalID
|
|
|| [globalID isTemporary])
|
|
{
|
|
globalIDs = nil;
|
|
break;
|
|
}
|
|
GDL2_AddObjectWithImpPtr(globalIDs,&aoIMP,globalID);
|
|
}
|
|
|
|
[updatedEoDbOp recordToManySnapshot:globalIDs
|
|
relationshipName:[relationship name]];
|
|
}
|
|
[self nullifyAttributesInRelationship:substRelationship
|
|
sourceObject:eo
|
|
destinationObjects:oldValues];
|
|
}
|
|
}
|
|
}
|
|
else if (updatedSnapValue != currentSnapValue)
|
|
{
|
|
[self nullifyAttributesInRelationship:substRelationship
|
|
sourceObject:eo
|
|
destinationObject:currentSnapValue];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} // for updated objs
|
|
|
|
for (i = [insertedAndUpdatedObjects count]; i > 0; i--)
|
|
{
|
|
EOCustomObject * updatedEO = [insertedAndUpdatedObjects objectAtIndex:i-1];
|
|
EOEntity * updatedEntity = [_database entityForObject:updatedEO];
|
|
if (updatedEntity != nil)
|
|
{
|
|
NSDictionary * updateSnapDict = nil;
|
|
NSDictionary * comSnapUpdatedEO = nil;
|
|
NSArray * updatedEntityRelationships = nil;
|
|
NSDictionary * updatedEntitySnapshot = nil;
|
|
NSUInteger updatedEntityRelCount = 0;
|
|
NSUInteger relIdx = 0;
|
|
|
|
[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!=nil)
|
|
{
|
|
if ([[updatedEntity classProperties] indexOfObject:substitutionRel] != NSNotFound)
|
|
{
|
|
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;
|
|
NSArray * newValues = nil;
|
|
|
|
//Here we use optimized method
|
|
//Think you may have more than 1000 objects in each of these arrays so imagine a 1000x1000 comparisons...
|
|
BOOL isDiff=[NSArray diffOldArray:currentSnapValues
|
|
newArray:updatedSnapValues
|
|
returnsRemovedValues:NULL
|
|
addedValues:&newValues];
|
|
if (isDiff)
|
|
{
|
|
NSUInteger newValuesCount=[newValues count];
|
|
if (newValuesCount>0)
|
|
{
|
|
if (_useToManyCaching )
|
|
{
|
|
IMP updatedSnapValues_oaiIMP=NULL;
|
|
IMP aoIMP=NULL;
|
|
EODatabaseOperation * updatedEoDbOp = [self databaseOperationForObject:updatedEO];
|
|
NSUInteger valCount = [updatedSnapValues count];
|
|
NSUInteger x = 0;
|
|
NSMutableArray * globalIDs = [NSMutableArray arrayWithCapacity:valCount];
|
|
|
|
for (; x < valCount; x++)
|
|
{
|
|
EOCustomObject * anObject = GDL2_ObjectAtIndexWithImpPtr(updatedSnapValues,&updatedSnapValues_oaiIMP,x);
|
|
EOGlobalID * globalID = [_editingContext globalIDForObject:anObject];
|
|
|
|
if (!globalID
|
|
|| [globalID isTemporary])
|
|
{
|
|
globalIDs = nil;
|
|
break;
|
|
}
|
|
GDL2_AddObjectWithImpPtr(globalIDs,&aoIMP,globalID);
|
|
}
|
|
//TODO: why we should record updatedSnapValues snapshot only when there's newValues ?
|
|
[updatedEoDbOp recordToManySnapshot:globalIDs
|
|
relationshipName:[updatedEntityRel name]];
|
|
}
|
|
[self relayAttributesInRelationship:substitutionRel
|
|
sourceObject:updatedEO
|
|
destinationObjects:newValues];
|
|
}
|
|
}
|
|
}
|
|
else 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]];
|
|
}
|
|
|
|
}
|
|
DESTROY(_checkPropagatedPKs);
|
|
}
|
|
|
|
|
|
/** 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!=nil)
|
|
{
|
|
[dbOpe setDatabaseOperator:EODatabaseUpdateOperator];
|
|
if ([changes count]>0)
|
|
{
|
|
[[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
|
|
{
|
|
NSMutableDictionary* values=nil;
|
|
|
|
EOEntity* entity = [_database entityForObject:object];
|
|
if (entity == nil)
|
|
{
|
|
values = AUTORELEASE([[_coordinator valuesForKeys:keys
|
|
object: object] mutableCopy]);
|
|
}
|
|
else
|
|
{
|
|
int keysCount = [keys count];
|
|
//If object is a fault: first try to see if needed values are all in PK
|
|
if (_isFault(object)
|
|
&& keysCount <= 16)
|
|
{
|
|
NSUInteger index = NSNotFound;
|
|
NSArray* pkAttrNames = [entity primaryKeyAttributeNames];
|
|
if ([pkAttrNames count] >= keysCount)
|
|
{
|
|
EOKeyGlobalID* gid = (EOKeyGlobalID*)[self _globalIDForObject:object];
|
|
id* pkValues = [gid keyValues];
|
|
id resultKeys[keysCount];
|
|
id resultValues[keysCount];
|
|
int j=0;
|
|
for(j=0; j < keysCount; j++)
|
|
{
|
|
NSString* key = [keys objectAtIndex:j];
|
|
index = [pkAttrNames indexOfObject:key];
|
|
if(index == NSNotFound)
|
|
break;
|
|
resultKeys[j] = key;
|
|
resultValues[j] = pkValues[index];
|
|
}
|
|
|
|
if (index != NSNotFound)//All keys found !
|
|
{
|
|
values=[NSMutableDictionary dictionaryWithObjects:resultValues
|
|
forKeys:resultKeys
|
|
count:keysCount];
|
|
}
|
|
}
|
|
}
|
|
if (values==nil)
|
|
{
|
|
EODatabaseOperation* dbOpe = [self databaseOperationForObject:object];
|
|
NSMutableDictionary* newRow = [dbOpe newRow];
|
|
values = AUTORELEASE([[newRow valuesForKeys:keys]mutableCopy]);
|
|
}
|
|
}
|
|
return values;
|
|
}
|
|
|
|
// private
|
|
-(NSMutableDictionary*)_mutableValuesForKeys: (NSArray *)keys
|
|
object: (id)object
|
|
{
|
|
return (NSMutableDictionary*)[self valuesForKeys:keys
|
|
object:object];
|
|
}
|
|
|
|
-(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;
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
- (void) _recordInsertForIntermediateRowFromSourceObject:(EOCustomObject*) sourceObject
|
|
relationship:(EORelationship*) relationship
|
|
destinationObject:(EOCustomObject*) destinationObject
|
|
{
|
|
EODatabaseOperation * op;
|
|
|
|
op = [self _databaseOperationForIntermediateRowFromSourceObject:sourceObject
|
|
relationship:relationship
|
|
destinationObject:destinationObject];
|
|
|
|
[op setDatabaseOperator:EODatabaseInsertOperator];
|
|
}
|
|
|
|
- (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
|
|
destinationObject: (id)destinationObject
|
|
{
|
|
if (destinationObject)
|
|
{
|
|
//Get SourceObject database operation
|
|
EODatabaseOperation *sourceDBOpe = [self databaseOperationForObject: sourceObject];
|
|
|
|
if ([relationship isToManyToOne])
|
|
{
|
|
[self _recordDeleteForIntermediateRowFromSourceObject: sourceObject
|
|
relationship: relationship
|
|
destinationObject: destinationObject];
|
|
}
|
|
else
|
|
{
|
|
// Key a dictionary of two array: destinationKeys and sourceKeys
|
|
NSDictionary *sourceToDestinationKeyMap =
|
|
[relationship _sourceToDestinationKeyMap]; //{destinationKeys = (customerCode); sourceKeys = (code); }
|
|
BOOL foreignKeyInDestination = [relationship foreignKeyInDestination];
|
|
|
|
if (foreignKeyInDestination)
|
|
{
|
|
NSArray *destinationKeys = [sourceToDestinationKeyMap
|
|
objectForKey: @"destinationKeys"];//(customerCode)
|
|
NSDictionary* changes= [NSDictionary dictionaryWithNullValuesForKeys:destinationKeys];
|
|
|
|
[self recordUpdateForObject: destinationObject
|
|
changes: changes];
|
|
}
|
|
else
|
|
{
|
|
NSArray* sourceKeys = [sourceToDestinationKeyMap objectForKey:@"sourceKeys"];
|
|
NSDictionary* changes= [NSDictionary dictionaryWithNullValuesForKeys:sourceKeys];
|
|
[[sourceDBOpe newRow]addEntriesFromDictionary:changes];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- (void)nullifyAttributesInRelationship: (EORelationship*)relationship
|
|
sourceObject: (id)sourceObject
|
|
destinationObjects: (NSArray*)destinationObjects
|
|
{
|
|
int 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 nullifyAttributesInRelationship: relationship
|
|
sourceObject: sourceObject
|
|
destinationObject: object];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)relayAttributesInRelationship: (EORelationship*)relationship
|
|
sourceObject: (id)sourceObject
|
|
destinationObjects: (NSArray*)destinationObjects
|
|
{
|
|
int 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: relationship
|
|
sourceObject: sourceObject
|
|
destinationObject: object];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSDictionary*)relayAttributesInRelationship: (EORelationship*)relationship
|
|
sourceObject: (id)sourceObject
|
|
destinationObject: (id)destinationObject
|
|
{
|
|
NSMutableDictionary *relayedValues = nil;
|
|
|
|
NSDebugMLLog(@"EODatabaseContext", @"sourceObject %p=%@ (class=%@)",
|
|
sourceObject, sourceObject, [sourceObject class]);
|
|
NSDebugMLLog(@"EODatabaseContext", @"destinationObject %p=%@ (class=%@)",
|
|
destinationObject, destinationObject,
|
|
[destinationObject class]);
|
|
|
|
if (destinationObject!=nil)
|
|
{
|
|
//Get SourceObject database operation
|
|
EODatabaseOperation* sourceDBOpe = [self databaseOperationForObject: sourceObject];
|
|
|
|
if ([relationship isToManyToOne])
|
|
{
|
|
[self _recordInsertForIntermediateRowFromSourceObject:sourceObject
|
|
relationship:relationship
|
|
destinationObject:destinationObject];
|
|
}
|
|
else
|
|
{
|
|
NSDictionary* sourceToDestinationKeyMap = [relationship _sourceToDestinationKeyMap];
|
|
NSArray* sourceKeys = [sourceToDestinationKeyMap objectForKey:@"sourceKeys"];
|
|
NSArray* destinationKeys = [sourceToDestinationKeyMap objectForKey:@"destinationKeys"];
|
|
if ([relationship foreignKeyInDestination])
|
|
{
|
|
relayedValues = AUTORELEASE([[[sourceDBOpe newRow] valuesForKeys:sourceKeys] mutableCopy]);
|
|
[relayedValues translateFromKeys:sourceKeys
|
|
toKeys:destinationKeys];
|
|
[self recordUpdateForObject:destinationObject
|
|
changes:relayedValues];
|
|
}
|
|
else
|
|
{
|
|
relayedValues = [self _mutableValuesForKeys:destinationKeys
|
|
object:destinationObject];
|
|
[relayedValues translateFromKeys:destinationKeys
|
|
toKeys:sourceKeys];
|
|
[[sourceDBOpe newRow] addEntriesFromDictionary:relayedValues];
|
|
}
|
|
}
|
|
}
|
|
|
|
return relayedValues;
|
|
};
|
|
|
|
- (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
|
|
{
|
|
EODatabaseOperation *databaseOpe = nil;
|
|
EOGlobalID *gid = EODatabaseContext_globalIDForObjectWithImpPtr(self,NULL,object);
|
|
|
|
databaseOpe = [self databaseOperationForGlobalID: gid];
|
|
|
|
if (databaseOpe == nil)
|
|
{
|
|
EOEntity *entity = [_database entityForObject: object];
|
|
if (entity != nil)
|
|
{
|
|
NSArray *primaryKeyAttributes = [entity primaryKeyAttributes];
|
|
if ([primaryKeyAttributes count]==0)
|
|
{
|
|
[NSException raise: @"NSIllegalStateException"
|
|
format:@"databaseOperationForObject: attempted to process an EO mapped to entity '%@' which has no primary key defined. All entities must have a primary key specified. You should run the EOModeler consistency checker on the model containing this entity and perform whatever actions are necessary to ensure that the model is in a consistent state.",
|
|
[entity name]];
|
|
}
|
|
else
|
|
{
|
|
NSDictionary *snapshot = nil;
|
|
NSArray *classPropertyNames = nil;
|
|
NSUInteger propNamesCount = 0;
|
|
NSMutableDictionary *objectStoredKVs = nil;
|
|
NSMutableDictionary *newRow = nil;
|
|
|
|
databaseOpe = [EODatabaseOperation
|
|
databaseOperationWithGlobalID: gid
|
|
object: object
|
|
entity: entity];
|
|
|
|
snapshot = EODatabaseContext_snapshotForGlobalIDWithImpPtr(self,NULL,gid);
|
|
|
|
if (snapshot == nil)
|
|
snapshot = [NSDictionary dictionary];
|
|
|
|
[databaseOpe setDBSnapshot: snapshot];
|
|
|
|
classPropertyNames = [entity classPropertyNames];
|
|
propNamesCount = [classPropertyNames count];
|
|
|
|
objectStoredKVs = (NSMutableDictionary*)[NSMutableDictionary dictionaryWithCapacity:propNamesCount];
|
|
|
|
if (propNamesCount>0)
|
|
{
|
|
NSUInteger i=0;
|
|
IMP oaiIMP=NULL;
|
|
for (i = 0; i < propNamesCount; i++)
|
|
{
|
|
NSString *key = GDL2_ObjectAtIndexWithImpPtr(classPropertyNames,&oaiIMP,i);
|
|
id value = [object storedValueForKey: key];
|
|
[objectStoredKVs setObject: (value==nil ? GDL2_EONull : value)
|
|
forKey: key];
|
|
}
|
|
};
|
|
newRow = AUTORELEASE([[NSMutableDictionary alloc]
|
|
initWithDictionary: snapshot
|
|
copyItems: NO]);
|
|
|
|
[newRow overrideEntriesWithObjectsFromDictionary:objectStoredKVs
|
|
forKeys:[entity dbSnapshotKeys]];
|
|
|
|
[databaseOpe setNewRow: newRow];
|
|
[self recordDatabaseOperation: databaseOpe];
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
BOOL hasNullPKValue = NO;
|
|
NSEnumerator *valuesEnum=nil;
|
|
IMP valuesNextObjectIMP=NULL;
|
|
id value=nil;
|
|
|
|
NSAssert3(destObject,
|
|
@"No destinationObject. pk=%@ relationship=%@ sourceObject=%@",
|
|
pk,relationship,sourceObject);
|
|
|
|
destAttributes = [relationship destinationAttributes];
|
|
|
|
|
|
destAttributeNames = [destAttributes resultsOfPerformingSelector:
|
|
@selector(name)];
|
|
|
|
keyValues = [self valuesForKeys: destAttributeNames
|
|
object: destObject];
|
|
|
|
valuesEnum = [keyValues objectEnumerator];
|
|
|
|
while ((value = GDL2_NextObjectWithImpPtr(valuesEnum,&valuesNextObjectIMP)))
|
|
{
|
|
if (_isNilOrEONull(value))
|
|
{
|
|
hasNullPKValue=YES;
|
|
break;
|
|
}
|
|
};
|
|
|
|
if (hasNullPKValue)
|
|
{
|
|
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
|
|
{
|
|
NSArray *relationships = [entity relationships];
|
|
NSArray *classPropertyNames = [entity classPropertyNames];
|
|
EODatabaseOperation *dbOpe = [self databaseOperationForObject: object];
|
|
NSDictionary *dbSnapshot = nil;
|
|
int count=0;
|
|
|
|
if (dbOpe)
|
|
dbSnapshot = [dbOpe dbSnapshot];
|
|
else
|
|
dbSnapshot = [NSDictionary dictionary];
|
|
|
|
if (relationships)
|
|
count = [relationships count];
|
|
|
|
if (count>0)
|
|
{
|
|
int i=0;
|
|
IMP oaiIMP=[relationships methodForSelector: @selector(objectAtIndex:)];
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
EORelationship *relationship = GDL2_ObjectAtIndexWithImp(relationships,oaiIMP,i);
|
|
EORelationship *substRelationship = [relationship _substitutionRelationshipForRow: dbSnapshot];
|
|
if (substRelationship!=nil
|
|
&& [substRelationship propagatesPrimaryKey])
|
|
{
|
|
NSString* relName=[substRelationship name];
|
|
if ([classPropertyNames containsObject:relName])
|
|
{
|
|
id storedValue = [object storedValueForKey:relName];
|
|
if (storedValue!=nil)
|
|
{
|
|
id snapshot = [self _currentCommittedSnapshotForObject: object];
|
|
id comSnapshotValue = [snapshot objectForKey:relName];
|
|
|
|
// or use == ?
|
|
if ([storedValue isEqual:comSnapshotValue]==NO)
|
|
{
|
|
if ([substRelationship isToMany])
|
|
{
|
|
// or use == ?
|
|
if ([comSnapshotValue isEqual: (NSArray*)storedValue] == NO)
|
|
{
|
|
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: storedValue
|
|
relationship: substRelationship];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (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;
|
|
NSUInteger count=0;
|
|
EODatabaseOperator dbOperator = EODatabaseNothingOperator;
|
|
EOEntity *entity = [dbOpe entity];
|
|
NSMutableDictionary* attributesByPath=nil;
|
|
|
|
[self processSnapshotForDatabaseOperation: dbOpe];
|
|
dbOperator = [dbOpe databaseOperator];
|
|
|
|
if (dbOperator == EODatabaseUpdateOperator)
|
|
{
|
|
NSDictionary* rowDiffs = [dbOpe rowDiffs];
|
|
if (rowDiffs==nil
|
|
|| [rowDiffs count] == 0)
|
|
{
|
|
//Nothing to update !
|
|
return;
|
|
}
|
|
}
|
|
|
|
attributesToSave = [entity _attributesToSave];
|
|
|
|
count = [attributesToSave count];
|
|
if (count>0)
|
|
{
|
|
NSString* EOFRootPath=@"__EOF_root__";
|
|
|
|
int i=0;
|
|
IMP attributesAddObjectIMP=NULL;
|
|
IMP attributesToSaveObjectAtIndexIMP=[attributesToSave methodForSelector:@selector(objectAtIndex:)];
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
EOAttribute *attribute =
|
|
GDL2_ObjectAtIndexWithImp(attributesToSave,
|
|
attributesToSaveObjectAtIndexIMP,
|
|
i);
|
|
NSString* path=nil;
|
|
if ([attribute isFlattened])
|
|
{
|
|
path=[attribute relationshipPath];
|
|
}
|
|
else
|
|
{
|
|
if ([attribute isDerived])
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
path=EOFRootPath;
|
|
}
|
|
NSMutableArray* attributesForPath=[attributesByPath objectForKey:path];
|
|
if (attributesForPath==nil)
|
|
{
|
|
if (attributesByPath==nil)
|
|
attributesByPath=[NSMutableDictionary dictionary];
|
|
|
|
attributesForPath=[NSMutableArray array];
|
|
[attributesByPath setObject:attributesForPath
|
|
forKey:path];
|
|
}
|
|
|
|
if (!attributesAddObjectIMP)
|
|
attributesAddObjectIMP=[attributesForPath methodForSelector:@selector(addObject:)];
|
|
|
|
GDL2_AddObjectWithImp(attributesForPath,
|
|
attributesAddObjectIMP,
|
|
attribute);
|
|
}
|
|
|
|
NSMutableDictionary* dbOpeNewRow=[dbOpe newRow];
|
|
NSEnumerator* pathesEnum = [attributesByPath keyEnumerator];
|
|
NSString* path=nil;
|
|
while((path=[pathesEnum nextObject]))
|
|
{
|
|
BOOL takeIt = YES;
|
|
BOOL addAttributes = NO;
|
|
if (path!=EOFRootPath)
|
|
{
|
|
NSString* aPath=nil;
|
|
for(aPath=path;
|
|
aPath!=nil;
|
|
aPath=[aPath relationshipPathByDeletingLastComponent])
|
|
{
|
|
EORelationship* relationship = [entity relationshipForPath:aPath];
|
|
if ([relationship isMandatory]
|
|
|| [relationship isParentRelationship])
|
|
continue;
|
|
takeIt = NO;
|
|
break;
|
|
}
|
|
}
|
|
NSMutableArray* attributes = [attributesByPath objectForKey:path];
|
|
int attributesCount=[attributes count];
|
|
int i=0;
|
|
for(i=0;i<attributesCount;i++)
|
|
{
|
|
EOAttribute* attribute = [attributes objectAtIndex:i];
|
|
NSString* attributeName = [attribute name];
|
|
if (!takeIt
|
|
&& ![attribute isReadOnly]
|
|
&& !_isNilOrEONull([dbOpeNewRow objectForKey:attributeName])
|
|
&& ![attributeName hasPrefix:@"NeededByEOF"])
|
|
{
|
|
addAttributes = YES;
|
|
break;
|
|
}
|
|
if (!takeIt
|
|
|| [attribute isReadOnly]
|
|
|| [attributeName hasPrefix:@"NeededByEOF"])
|
|
continue;
|
|
addAttributes = YES;
|
|
break;
|
|
}
|
|
if (addAttributes)
|
|
[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=%"PRIuPTR" %p %@",
|
|
[object count],
|
|
object,
|
|
object);
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"NEAR FINISHED 1 objects count=%"PRIuPTR" %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=%"PRIuPTR" %p %@",
|
|
[object count],
|
|
object,
|
|
object);
|
|
|
|
[object addObjectsFromArray: objects];
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"NEAR FINISHED 2 object count=%"PRIuPTR" %@",
|
|
[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]=%"PRIuPTR,
|
|
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;
|
|
}
|
|
|
|
-(id)_fetchSingleObjectForEntity:(EOEntity*)entity
|
|
globalID:(EOGlobalID*)gid
|
|
editingContext:(EOEditingContext*)context
|
|
{
|
|
id object=nil;
|
|
NSDictionary* pk = [entity primaryKeyForGlobalID:gid];
|
|
EOFetchSpecification* fetchSpec = [EOFetchSpecification fetchSpecificationWithEntityName:[entity name]
|
|
qualifier: [entity qualifierForPrimaryKey:pk]
|
|
sortOrderings: nil];
|
|
[fetchSpec setFetchLimit:1];
|
|
NSArray* objects = [self objectsWithFetchSpecification:fetchSpec
|
|
editingContext: context];
|
|
if ([objects count]==0)
|
|
{
|
|
[NSException raise: @"NSIllegalStateException"
|
|
format:@"The object with globalID %@ could not be found in the database. This could be result of a referential integrity problem with the database. An empty fault could not be created because the object's class could not be determined (e.g. the GID is temporary or it is for an abstract entity).",
|
|
gid];
|
|
}
|
|
else
|
|
object=[objects objectAtIndex:0];
|
|
|
|
return object;
|
|
}
|
|
|
|
-(id)_objectFaultWithSnapshot:(NSDictionary*) snapshot
|
|
relationship:(EORelationship*) relationship
|
|
editingContext:(EOEditingContext*)context
|
|
{
|
|
id objectFault=nil;
|
|
EOMutableKnownKeyDictionary *foreignKeyForSourceRow =
|
|
[relationship _foreignKeyForSourceRow: snapshot];
|
|
|
|
if ([foreignKeyForSourceRow containsObjectsNotIdenticalTo: GDL2_EONull])
|
|
{
|
|
EOEntity *destinationEntity = [relationship destinationEntity];
|
|
EOGlobalID *relRowGid = [destinationEntity
|
|
globalIDForRow: foreignKeyForSourceRow];
|
|
|
|
if ([[destinationEntity subEntities]count] > 0)
|
|
{
|
|
objectFault=[self _fetchSingleObjectForEntity:destinationEntity
|
|
globalID:relRowGid
|
|
editingContext:context];
|
|
}
|
|
else
|
|
{
|
|
objectFault=[context faultForGlobalID: relRowGid
|
|
editingContext: context];
|
|
}
|
|
}
|
|
return objectFault;
|
|
}
|
|
|
|
- (void)initializeObject: (id)object
|
|
row: (NSDictionary*)row
|
|
entity: (EOEntity*)entity
|
|
editingContext: (EOEditingContext*)context
|
|
{
|
|
NSArray *relationships = nil;
|
|
NSArray *classPropertyAttributeNames = [entity classPropertyAttributeNames];
|
|
NSUInteger count = [classPropertyAttributeNames count];
|
|
IMP rowObjectForKeyIMP=NULL;
|
|
|
|
//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 = 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
|
|
{
|
|
relObject=[self _objectFaultWithSnapshot: row
|
|
relationship: relationship
|
|
editingContext: context];
|
|
}
|
|
|
|
[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 (channel==nil)
|
|
{
|
|
[NSException raise: @"NSIllegalStateException"
|
|
format:@"%s: no database channel is available",
|
|
__PRETTY_FUNCTION__];
|
|
|
|
}
|
|
else if (![self _openChannelWithLoginPanel: channel])
|
|
{
|
|
[NSException raise: @"NSIllegalStateException"
|
|
format:@"%s: failed to open database channel",
|
|
__PRETTY_FUNCTION__];
|
|
|
|
}
|
|
|
|
return channel;
|
|
}
|
|
|
|
- (BOOL) _openChannelWithLoginPanel: (EODatabaseChannel*)databaseChannel
|
|
{
|
|
BOOL result=NO;
|
|
EOAdaptorChannel *adaptorChannel = [databaseChannel adaptorChannel];
|
|
|
|
if ([adaptorChannel isOpen])
|
|
result=YES;
|
|
else
|
|
{
|
|
NSException* exception=nil;
|
|
NS_DURING
|
|
{
|
|
[adaptorChannel openChannel];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
if (![[localException name] isEqualToString:EOGeneralAdaptorException])
|
|
[localException raise];
|
|
else
|
|
NSLog(@"%@",localException);
|
|
}
|
|
NS_ENDHANDLER;
|
|
|
|
if ([adaptorChannel isOpen])
|
|
result=YES;
|
|
else
|
|
[exception raise];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (void) _forceDisconnect
|
|
{
|
|
EOAdaptorContext* adaptorContext = [self adaptorContext];
|
|
NSArray* channels = [adaptorContext channels];
|
|
NSArray* registeredChannels = [self registeredChannels];
|
|
|
|
int i = 0;
|
|
int c=[channels count];
|
|
for(i=0;i<c;i++)
|
|
{
|
|
NS_DURING
|
|
{
|
|
[[channels objectAtIndex:i] closeChannel];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"%@",localException);
|
|
}
|
|
NS_ENDHANDLER;
|
|
}
|
|
|
|
c=[registeredChannels count];
|
|
for(i=0;i<c;i++)
|
|
{
|
|
NS_DURING
|
|
{
|
|
[self unregisterChannel:[registeredChannels objectAtIndex:i]];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"%@",localException);
|
|
}
|
|
NS_ENDHANDLER;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation EODatabaseContext(EOMultiThreaded)
|
|
|
|
- (void)lock
|
|
{
|
|
[_lock lock];
|
|
}
|
|
|
|
- (void)unlock
|
|
{
|
|
[_lock unlock];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation EODatabaseContext (EODatabaseContextPrivate2)
|
|
|
|
- (void) _verifyNoChangesToReadonlyEntity: (EODatabaseOperation*)dbOpe
|
|
{
|
|
EOEntity* entity = [dbOpe entity];
|
|
if ([entity isReadOnly])
|
|
{
|
|
switch([dbOpe databaseOperator])
|
|
{
|
|
case EOAdaptorInsertOperator:
|
|
[NSException raise: @"NSIllegalStateException"
|
|
format:@"cannot insert object: %@ that corresponds to read-only entity: %@ in databaseContext %@",
|
|
[dbOpe object],
|
|
[entity name],
|
|
self];
|
|
break;
|
|
case EOAdaptorDeleteOperator:
|
|
[NSException raise: @"NSIllegalStateException"
|
|
format:@"cannot insert delete: %@ that corresponds to read-only entity: %@ in databaseContext %@",
|
|
[dbOpe object],
|
|
[entity name],
|
|
self];
|
|
break;
|
|
case EOAdaptorUpdateOperator:
|
|
if (![[dbOpe dbSnapshot] isEqual:[dbOpe newRow]])
|
|
{
|
|
[NSException raise: @"NSIllegalStateException"
|
|
format:@"cannot update '%@' keys on object %@ that corresponds to read-only entity: %@ in databaseContext %@",
|
|
[[dbOpe rowDiffsForAttributes:[entity attributes]] allKeys],
|
|
[dbOpe object],
|
|
[entity name],
|
|
self];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else if ([dbOpe databaseOperator] == EOAdaptorUpdateOperator
|
|
&& [entity hasNonUpdateableAttributes])
|
|
{
|
|
NSArray* dbSnapshotKeys = [entity dbSnapshotKeys];
|
|
NSDictionary* dbSnapshot = [dbOpe dbSnapshot];
|
|
NSMutableDictionary* newRow = [dbOpe newRow];
|
|
int i=0;
|
|
int c=[dbSnapshotKeys count];
|
|
for(i=0;i<c;i++)
|
|
{
|
|
NSString* key = [dbSnapshotKeys objectAtIndex:i];
|
|
EOAttribute* attribute = [entity attributeNamed:key];
|
|
if ([attribute _isNonUpdateable]
|
|
&& ![[dbSnapshot objectForKey:key] isEqual:[newRow objectForKey:key]])
|
|
{
|
|
[NSException raise: @"NSIllegalStateException"
|
|
format:@"cannot update %@ '%@' on object: %@ that corresponds to read-onlywof entity: %@ in databaseContext %@",
|
|
([attribute isReadOnly] ? @"read-only key" : @"primary-key"),
|
|
key,
|
|
[dbOpe object],
|
|
[entity name],
|
|
self];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (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];
|
|
NSAssert3(objectEditingContext, @"No editing context for object %p of class %@: %@",
|
|
object,[object class],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];
|
|
NSMutableSet *entitiesToProcess = nil;
|
|
NSUInteger which;
|
|
|
|
if (_nonPrimaryKeyGenerators)
|
|
NSResetHashTable(_nonPrimaryKeyGenerators);
|
|
|
|
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 (entity)
|
|
{
|
|
NSString* entityName=[entity name];
|
|
if (![entitiesToProcess containsObject:entityName])
|
|
{
|
|
NSArray* relationships=[entity relationships];
|
|
int relationshipsCount=[relationships count];
|
|
int j=0;
|
|
if (entitiesToProcess==nil)
|
|
entitiesToProcess = [NSMutableSet set];
|
|
[entitiesToProcess addObject: entity];
|
|
for(j=0;j<relationshipsCount;j++)
|
|
{
|
|
EORelationship* relationship = [relationships objectAtIndex:j];
|
|
if ([relationship propagatesPrimaryKey])
|
|
{
|
|
EOEntity* dstEntity=[relationship destinationEntity];
|
|
NSMutableArray* tmpEntities = [NSMutableArray arrayWithObject:entity];
|
|
NSMutableArray* tmpRelationships = [NSMutableArray array];
|
|
[EOEntity _assertNoPropagateKeyCycleWithEntities:tmpEntities
|
|
relationships:tmpRelationships];
|
|
if (!_nonPrimaryKeyGenerators)
|
|
_nonPrimaryKeyGenerators = NSCreateHashTable(NSObjectHashCallBacks, 32);
|
|
|
|
NSHashInsertIfAbsent(_nonPrimaryKeyGenerators, [dstEntity name]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
NSDebugMLLog(@"EODatabaseContext",
|
|
@"_nonPrimaryKeyGenerators=%@",
|
|
NSStringFromHashTable(_nonPrimaryKeyGenerators));
|
|
}
|
|
|
|
/** 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
|