4 Copyright (C) 1996 Free Software Foundation, Inc.
6 Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>
9 This file is part of the GNUstep Database Library.
11 This library is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Library General Public
13 License as published by the Free Software Foundation; either
14 version 2 of the License, or (at your option) any later version.
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Library General Public License for more details.
21 You should have received a copy of the GNU Library General Public
22 License along with this library; see the file COPYING.LIB.
23 If not, write to the Free Software Foundation,
24 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 #import "EODatabaseChannel.h"
30 #import "EOAdaptorChannel.h"
31 #import "EOAdaptorContext.h"
32 #import "EOAttribute.h"
33 #import "EODatabase.h"
34 #import "EODatabaseContext.h"
36 #import "EODatabaseFault.h"
37 #import "EOGenericRecord.h"
39 #import "EOObjectUniquer.h"
40 #import "EOSQLQualifier.h"
41 #import "EORelationship.h"
42 #import <EOControl/EONull.h>
43 #import <EOControl/EOFetchSpecification.h>
44 #import <EOControl/EOKeyValueCoding.h>
46 @class EOGenericRecord;
48 NSString *EODatabaseChannelWillOpenNotificationName =
49 @"EODatabaseChannelWillOpenNotification";
50 NSString *EODatabaseChannelDidOpenNotificationName =
51 @"EODatabaseChannelDidOpenNotification";
52 NSString *EODatabaseChannelWillCloseNotificationName =
53 @"EODatabaseChannelWillCloseNotification";
54 NSString *EODatabaseChannelDidCloseNotificationName =
55 @"EODatabaseChannelDidCloseNotification";
56 NSString *EODatabaseChannelCouldNotOpenNotificationName =
57 @"EODatabaseChannelCouldNotOpenNotification";
58 NSString *EODatabaseChannelWillInsertObjectName =
59 @"EODatabaseChannelWillInsertObjectName";
60 NSString *EODatabaseChannelDidInsertObjectName =
61 @"EODatabaseChannelDidInsertObjectName";
62 NSString *EODatabaseChannelWillUpdateObjectName =
63 @"EODatabaseChannelWillUpdateObjectName";
64 NSString *EODatabaseChannelDidUpdateObjectName =
65 @"EODatabaseChannelDidUpdateObjectName";
66 NSString *EODatabaseChannelWillDeleteObjectName =
67 @"EODatabaseChannelWillDeleteObjectName";
68 NSString *EODatabaseChannelDidDeleteObjectName =
69 @"EODatabaseChannelDidDeleteObjectName";
70 NSString *EODatabaseChannelWillLockObjectName =
71 @"EODatabaseChannelWillLockObjectName";
72 NSString *EODatabaseChannelDidLockObjectName =
73 @"EODatabaseChannelDidLockObjectName";
76 * Private methods declaration
79 @interface EODatabaseChannel(Private)
80 - (id)privateFetchWithZone:(NSZone *)_zone;
81 - (Class)privateClassForEntity:(EOEntity *)anEntity;
82 - (void)privateUpdateCurrentEntityInfo;
83 - (void)privateClearCurrentEntityInfo;
84 - (void)privateReportError:(SEL)method:(NSString *)format, ...;
88 * EODatabaseChannel implementation
91 @implementation EODatabaseChannel
94 * Initializing a new instance
97 - (id)initWithDatabaseContext:(EODatabaseContext *)_dbContext {
98 if (_dbContext == nil) {
103 self->notificationCenter = RETAIN([NSNotificationCenter defaultCenter]);
105 self->databaseContext = RETAIN(_dbContext);
106 [self setDelegate:[self->databaseContext delegate]];
107 [self->databaseContext channelDidInit:self];
113 [self->databaseContext channelWillDealloc:self];
114 RELEASE(self->currentEditingContext);
115 RELEASE(self->databaseContext);
116 RELEASE(self->adaptorChannel);
117 RELEASE(self->notificationCenter);
123 - (void)postNotification:(NSString *)_name {
124 [self->notificationCenter postNotificationName:_name object:self];
126 - (void)postNotification:(NSString *)_name object:(id)_obj {
127 [self->notificationCenter postNotificationName:_name
129 userInfo:[NSDictionary dictionaryWithObject:_obj
135 - (EOAdaptorChannel *)adaptorChannel {
136 if (self->adaptorChannel == nil) {
137 static int reuseAdaptorCh = -1;
138 if (reuseAdaptorCh == -1) {
139 reuseAdaptorCh = [[[NSUserDefaults standardUserDefaults]
140 objectForKey:@"EOReuseAdaptorChannel"]
144 if (reuseAdaptorCh) {
145 NSEnumerator *channels;
146 EOAdaptorChannel *channel;
149 [[[[self databaseContext] adaptorContext] channels] objectEnumerator];
151 while ((channel = [channels nextObject])) {
152 if ([channel isFetchInProgress])
156 NSLog(@"reuse adaptor channel: %@", channel);
158 self->adaptorChannel = channel;
163 if (self->adaptorChannel == nil) {
164 self->adaptorChannel =
165 [[[self databaseContext] adaptorContext] createAdaptorChannel];
168 RETAIN(self->adaptorChannel);
170 return self->adaptorChannel;
173 - (EODatabaseContext *)databaseContext {
174 return self->databaseContext;
179 - (void)setDelegate:(id)_delegate {
180 self->delegate = _delegate;
183 return self->delegate;
186 // Opening and closing a channel
189 return [[self adaptorChannel] isOpen];
192 - (BOOL)openChannel {
195 [self postNotification:EODatabaseChannelWillOpenNotificationName];
197 if ((result = [[self adaptorChannel] openChannel])) {
198 self->successfulOpenCount++;
199 [self postNotification:EODatabaseChannelDidOpenNotificationName];
202 self->failedOpenCount++;
203 [self postNotification:EODatabaseChannelCouldNotOpenNotificationName];
209 - (void)closeChannel {
210 [self postNotification:EODatabaseChannelWillCloseNotificationName];
211 [[self adaptorChannel] closeChannel];
213 [self postNotification:EODatabaseChannelDidCloseNotificationName];
218 - (BOOL)_isNoRaiseOnModificationException:(NSException *)_exception {
219 /* for compatibility with non-X methods, translate some errors to a bool */
222 n = [_exception name];
223 if ([n isEqualToString:@"EOEvaluationError"])
225 if ([n isEqualToString:@"EODelegateRejects"])
231 - (BOOL)insertObject:(id)anObj {
232 // TODO: split up this method
233 // TODO: write an insertObjectX: which returns an exception
234 NSException *exception = nil;
235 EOEntity *entity = nil;
236 NSDictionary *pkey = nil;
237 NSDictionary *values = nil;
238 NSDictionary *snapshot = nil;
239 NSArray *attributes = nil;
242 [self postNotification:EODatabaseChannelWillInsertObjectName object:anObj];
244 if (![anObj prepareForInsertInChannel:self context:self->databaseContext])
247 // Check the delegate
248 if ([self->delegate respondsToSelector:
249 @selector(databaseChannel:willInsertObject:)])
250 anObj = [delegate databaseChannel:self willInsertObject:anObj];
252 // Check nil (delegate disallowes or given object was nil)
257 if ([EOFault isFault:anObj]) {
258 [NSException raise:NSInvalidArgumentException
259 format:@"Attempt to insert a fault in a database channel"];
262 // Check if we can insert
263 if ([databaseContext updateStrategy] == EONoUpdate) {
264 [self privateReportError:_cmd :
265 @"cannot insert if context has 'NoUpdate' update strategy."];
269 /* validate object for insert */
271 if ((exception = [anObj validateForInsert])) {
272 /* validation failed */
276 // Check if in a transaction
277 if (![databaseContext transactionNestingLevel]) {
278 [self privateReportError:_cmd :
279 @"cannot insert if contex has no transaction opened."];
284 entity = [anObj respondsToSelector:@selector(entity)]
286 : [[[[adaptorChannel adaptorContext] adaptor] model] entityForObject:anObj];
290 [self privateReportError:_cmd :
291 @"cannot determine entity for object %p class %@.",
292 anObj, NSStringFromClass([anObj class])];
295 if ([entity isReadOnly]) {
296 [self privateReportError:_cmd :
297 @"cannot insert object %p for readonly entity %@.",
298 anObj, [entity name]];
302 // Get array of attributes to insert
303 attributes = [entity attributesUsedForInsert];
305 // Get simple values and convert them to adaptor values
306 values = [anObj valuesForKeys:[entity attributesNamesUsedForInsert]];
307 values = [entity convertValuesToModel:values];
309 // Get and check *must insert* attributes (primary key, lock, readonly)
310 for (i = [attributes count]-1; i >= 0; i--) {
311 EOAttribute *attribute = [attributes objectAtIndex:i];
313 NSAssert(attribute, @"invalid attribute object ..");
315 if (![values objectForKey:[attribute name]]) {
316 [self privateReportError:_cmd :
317 @"null value for insert attribute %@ for object %@ entity %@",
318 [attribute name], anObj, [entity name]];
323 // Make primary key and snapshot
324 snapshot = [entity snapshotForRow:values];
325 if (snapshot == nil) {
326 [self privateReportError:_cmd :
327 @"cannot determine snapshot for %p from values %@ entity %@.",
328 anObj, [values description], [entity name]];
331 pkey = [entity primaryKeyForRow:values];
333 [self privateReportError:_cmd :
334 @"cannot determine primary key for %p from values %@ entity %@.",
335 anObj, [values description], [entity name]];
339 // Insert adaptor row
340 exception = [adaptorChannel insertRowX:values forEntity:entity];
342 if (![self _isNoRaiseOnModificationException:exception]) [exception raise];
346 // Record object in database context
347 [databaseContext recordObject:anObj
348 primaryKey:pkey entity:entity snapshot:values];
351 [anObj wasInsertedInChannel:self context:self->databaseContext];
354 if ([delegate respondsToSelector:@selector(databaseChannel:didInsertObject:)])
355 [delegate databaseChannel:self didInsertObject:anObj];
357 [self postNotification:EODatabaseChannelDidInsertObjectName object:anObj];
361 - (BOOL)updateObject:(id)anObj {
362 // TODO: split up this huge method
363 // TODO: make an updateObjectX: method which returns an exception
364 NSException *exception = nil;
365 EOEntity *entity = nil;
366 EOSQLQualifier *qualifier = nil;
367 NSDictionary *old_pkey = nil;
368 NSDictionary *old_snapshot = nil;
369 NSDictionary *new_pkey = nil;
370 NSDictionary *new_snapshot = nil;
371 NSDictionary *values = nil;
372 BOOL needsOptimisticLock;
374 [self postNotification:EODatabaseChannelWillUpdateObjectName object:anObj];
376 if (![anObj prepareForUpdateInChannel:self context:self->databaseContext])
379 // Check the delegate
380 if ([delegate respondsToSelector:@selector(databaseChannel:willUpdateObject:)])
381 anObj = [delegate databaseChannel:self willUpdateObject:anObj];
383 // Check nil (delegate disallowes or given object was nil)
388 if ([EOFault isFault:anObj]) {
389 [NSException raise:NSInvalidArgumentException
390 format:@"Attempt to update a fault in a database channel"];
393 // Check if we can update
394 if ([databaseContext updateStrategy] == EONoUpdate) {
395 [self privateReportError:_cmd :
396 @"cannot update if context has 'NoUpdate' update strategy."];
400 /* validate object for update */
402 if ((exception = [anObj validateForUpdate])) {
403 /* validation failed */
407 // Check if in a transaction
408 if (![databaseContext transactionNestingLevel]) {
409 [self privateReportError:_cmd :
410 @"cannot update if contex has no transaction opened."];
415 entity = [anObj respondsToSelector:@selector(entity)]
417 : [[[[adaptorChannel adaptorContext] adaptor] model] entityForObject:anObj];
422 [self privateReportError:_cmd :
423 @"cannot determine entity for object %p class %@.",
424 anObj, NSStringFromClass([anObj class])];
427 if ([entity isReadOnly]) {
428 [self privateReportError:_cmd :
429 @"cannot update object %p for readonly entity %@.",
430 anObj, [entity name]];
435 // Get and check old snapshot and primary key
437 [databaseContext primaryKey:&old_pkey
438 andSnapshot:&old_snapshot
441 if (old_snapshot == nil) {
442 [self privateReportError:_cmd :
443 @"cannot update object %p because there is no snapshot for it."];
447 old_pkey = [entity primaryKeyForRow:old_snapshot];
448 if (old_pkey == nil) {
449 [self privateReportError:_cmd :
450 @"cannot determine primary key for %p from snapshot %@ entity %@.",
451 anObj, [old_snapshot description], [entity name]];
456 // Get simple values and convert them to adaptor values
457 values = [anObj valuesForKeys:[entity attributesNamesUsedForInsert]];
458 values = [entity convertValuesToModel:values];
460 // Get and check new primary key and snapshot
462 new_snapshot = [entity snapshotForRow:values];
463 if (new_snapshot == nil) {
464 [self privateReportError:_cmd :
465 @"cannot determine snapshot for %p from values %@ entity %@.",
466 anObj, [values description], [entity name]];
469 new_pkey = [entity primaryKeyForRow:new_snapshot];
470 if (new_pkey == nil) {
471 [self privateReportError:_cmd :
472 @"cannot determine primary key for %p from values %@ entity %@.",
473 anObj, [values description], [entity name]];
478 // Check if we need to lock optimistic before update
479 // that is compare locking attributes with the existing ones in database
480 switch([databaseContext updateStrategy]) {
481 case EOUpdateWithOptimisticLocking:
482 case EOUpdateWithPessimisticLocking:
483 needsOptimisticLock = ![databaseContext isObjectLocked:anObj];
485 case EOUpdateWithNoLocking:
486 needsOptimisticLock = NO;
492 // If we need an "optimistic lock" then perform lock
493 // else just make the qualifier based on the primary key only
494 if (needsOptimisticLock) {
496 BOOL canUseQualifier = YES;
497 NSArray *lockAttrs = [entity attributesUsedForLocking];
498 EOAdaptor *adaptor = [[adaptorChannel adaptorContext] adaptor];
501 // Check if attributes used for locking can be used in a qualifier
502 for (i = [lockAttrs count]-1; i >= 0; i--) {
503 if (![adaptor isValidQualifierType:
504 [[lockAttrs objectAtIndex:i] externalType]]) {
505 canUseQualifier = NO;
511 // If YES just build the qualifier
512 qualifier = [EOSQLQualifier qualifierForRow:old_snapshot
515 // If NO then lock the row in the database server, fetch the
516 // new snapshot and compare it with the old one
517 qualifier = [EOSQLQualifier qualifierForPrimaryKey:old_pkey
520 NSAssert2([lockAttrs count] > 0,
521 @"missing locking attributes: lock=%@ object=%@",
524 if (![adaptorChannel selectAttributes:lockAttrs
525 describedByQualifier:qualifier
528 [self privateReportError:_cmd :
529 @"could not lock=%@ with qualifier=%@ entity=%@.",
530 anObj, [qualifier description], [entity name]];
533 row = [adaptorChannel fetchAttributes:lockAttrs withZone:NULL];
534 [adaptorChannel cancelFetch];
536 [self privateReportError:_cmd :
537 @"could not get row to lock %p with qualifier %@.",
538 anObj, [qualifier description]];
541 [databaseContext recordLockedObject:anObj];
542 if (![row isEqual:old_snapshot]) {
543 [self privateReportError:_cmd :
544 @"could not lock %p. Snapshots: self %@ database %@.",
545 anObj, [old_snapshot description], [row description]];
551 qualifier = [EOSQLQualifier qualifierForPrimaryKey:old_pkey
555 // Compute values as delta from values and old_snapshot
557 NSMutableDictionary *delta;
558 NSString *attributeName;
562 allKeys = [values allKeys];
563 delta = [NSMutableDictionary dictionary];
564 for (i = 0, count = [allKeys count]; i < count; i++) {
567 attributeName = [allKeys objectAtIndex:i];
568 new_v = [values objectForKey:attributeName];
569 old_v = [old_snapshot objectForKey:attributeName];
571 if ((old_v == nil) || ![new_v isEqual:old_v])
572 [delta setObject:new_v forKey:attributeName];
577 // no reason for update --> fetch to be sure, that no one has deleted it
578 // HH: The object was not changed, so we refetch to determine whether it
580 if ([values count] == 0) {
581 if (![self refetchObject:anObj])
588 ex = [adaptorChannel updateRowX:values describedByQualifier:qualifier];
590 if (![self _isNoRaiseOnModificationException:ex]) [ex raise];
594 // Record object in database context
595 if (![new_pkey isEqual:old_pkey]) {
596 NSLog(@"WARNING: (%@) primary key changed from %@ to %@",
597 __PRETTY_FUNCTION__, old_pkey, new_pkey);
598 [databaseContext forgetObject:anObj];
601 [databaseContext recordObject:anObj
604 snapshot:new_snapshot];
605 [databaseContext recordUpdatedObject:anObj];
608 [anObj wasUpdatedInChannel:self context:self->databaseContext];
611 if ([delegate respondsToSelector:@selector(databaseChannel:didUpdateObject:)])
612 [delegate databaseChannel:self didUpdateObject:anObj];
614 [self postNotification:EODatabaseChannelDidUpdateObjectName object:anObj];
618 - (BOOL)deleteObject:(id)anObj {
619 // TODO: split this method
620 // TODO: add an deleteObjectX: method which returns an NSException
621 NSException *exception = nil;
622 EOEntity *entity = nil;
623 NSDictionary *pkey = nil;
624 NSDictionary *snapshot = nil;
625 EOSQLQualifier *qualifier = nil;
627 [self postNotification:EODatabaseChannelWillDeleteObjectName object:anObj];
629 if (![anObj prepareForDeleteInChannel:self context:self->databaseContext])
632 // Check the delegate
633 if ([delegate respondsToSelector:@selector(databaseChannel:willDeleteObject:)])
634 anObj = [delegate databaseChannel:self willDeleteObject:anObj];
636 // Check nil (delegate disallowes or given object was nil)
641 if ([EOFault isFault:anObj]) {
642 [NSException raise:NSInvalidArgumentException
643 format:@"Attempt to delete a fault in a database channel"];
646 // Check if we can delete
647 if ([databaseContext updateStrategy] == EONoUpdate) {
648 [self privateReportError:_cmd :
649 @"cannot delete if context has 'NoUpdate' update strategy."];
653 /* validate object for delete */
655 if ((exception = [anObj validateForDelete])) {
656 /* validation failed */
660 // Check if in a transaction
661 if (![databaseContext transactionNestingLevel]) {
662 [self privateReportError:_cmd :
663 @"cannot update if contex has no transaction opened."];
668 entity = [anObj respondsToSelector:@selector(entity)]
670 : [[[[adaptorChannel adaptorContext] adaptor] model] entityForObject:anObj];
674 [self privateReportError:_cmd :
675 @"cannot determine entity for object %p class %s.",
676 anObj, NSStringFromClass([anObj class])];
679 if ([entity isReadOnly]) {
680 [self privateReportError:_cmd :
681 @"cannot delete object %p for readonly entity %@.",
682 anObj, [entity name]];
686 // Get snapshot and old primary key
687 [databaseContext primaryKey:&pkey andSnapshot:&snapshot forObject:anObj];
690 [self privateReportError:_cmd :
691 @"cannot delete object %p because there is no snapshot for it."];
692 pkey = [entity primaryKeyForRow:snapshot];
695 [self privateReportError:_cmd :
696 @"cannot determine primary key for %p from values %@ entity %@.",
697 anObj, [snapshot description], [entity name]];
701 // Get and check qualifier for object to delete
702 qualifier = [EOSQLQualifier qualifierForPrimaryKey:pkey entity:entity];
703 if (qualifier == nil) {
704 [self privateReportError:_cmd :
705 @"cannot make qualifier to delete %p primary key %@ entity %@.",
706 anObj, [pkey description], [entity name]];
710 // Delete adaptor row
711 exception = [adaptorChannel deleteRowsDescribedByQualifierX:qualifier];
712 if (exception != nil) {
713 if (![self _isNoRaiseOnModificationException:exception]) [exception raise];
717 AUTORELEASE(RETAIN(anObj));
719 // Forget object in database context
720 [databaseContext forgetObject:anObj];
723 [anObj wasDeletedInChannel:self context:self->databaseContext];
726 if ([delegate respondsToSelector:
727 @selector(databaseChannel:didDeleteObject:)])
728 [delegate databaseChannel:self didDeleteObject:anObj];
730 [self postNotification:EODatabaseChannelDidDeleteObjectName object:anObj];
734 - (BOOL)lockObject:(id)anObj {
735 EOEntity *entity = nil;
736 NSDictionary *pkey = nil;
737 NSDictionary *snapshot = nil;
738 EOSQLQualifier *qualifier = nil;
740 [self postNotification:EODatabaseChannelWillLockObjectName object:anObj];
742 if (![anObj prepareForLockInChannel:self context:self->databaseContext])
745 // Check the delegate
746 if ([delegate respondsToSelector:@selector(databaseChannel:willLockObject:)])
747 anObj = [delegate databaseChannel:self willLockObject:anObj];
749 // Check nil (delegate disallowes or given object was nil)
754 if ([EOFault isFault:anObj]) {
755 [NSException raise:NSInvalidArgumentException
756 format:@"Attempt to lock a fault in a database channel"];
759 // Check if we can lock
760 if ([databaseContext updateStrategy] == EONoUpdate) {
761 [self privateReportError:_cmd :
762 @"cannot lock if context has 'NoUpdate' update strategy."];
766 // Check if in a transaction
767 if (![databaseContext transactionNestingLevel]) {
768 [self privateReportError:_cmd :
769 @"cannot lock if contex has no transaction opened."];
773 // Check if fetch is in progress
774 if ([self isFetchInProgress]) {
775 [self privateReportError:_cmd :
776 @"cannot lock if contex has a fetch in progress."];
781 entity = [anObj respondsToSelector:@selector(entity)]
783 : [[[[adaptorChannel adaptorContext] adaptor] model] entityForObject:anObj];
787 [self privateReportError:_cmd :
788 @"cannot determine entity for object %p class %s.",
789 anObj, NSStringFromClass([anObj class])];
792 if ([entity isReadOnly]) {
793 [self privateReportError:_cmd :
794 @"cannot lock object %p for readonly entity %@.",
795 anObj, [entity name]];
799 // Get snapshot and old primary key
800 [databaseContext primaryKey:&pkey andSnapshot:&snapshot forObject:anObj];
801 if (snapshot == nil) {
802 [self privateReportError:_cmd :
803 @"cannot lock object %p because there is no snapshot for it."];
808 pkey = [entity primaryKeyForRow:snapshot];
811 [self privateReportError:_cmd :
812 @"cannot determine primary key for %p from values %@ entity %@.",
813 anObj, [snapshot description], [entity name]];
818 NSArray *lockAttrs = [entity attributesUsedForLocking];
819 NSDictionary *row = nil;
821 qualifier = [EOSQLQualifier qualifierForPrimaryKey:pkey entity:entity];
824 NSAssert2([lockAttrs count] > 0,
825 @"missing locking attributes: lock=%@ object=%@",
828 if (![adaptorChannel selectAttributes:lockAttrs
829 describedByQualifier:qualifier
832 [self privateReportError:_cmd :
833 @"could not lock %p with qualifier %@.",
834 anObj, [qualifier description]];
837 row = [adaptorChannel fetchAttributes:lockAttrs withZone:NULL];
838 [adaptorChannel cancelFetch];
840 [self privateReportError:_cmd :
841 @"could not lock %p with qualifier %@.",
842 anObj, [qualifier description]];
845 if (![row isEqual:snapshot]) {
846 [self privateReportError:_cmd :
847 @"could not lock %p. Snapshots: self %@ database %@.",
848 anObj, [snapshot description], [row description]];
853 // Register lock object in database context
854 [databaseContext recordLockedObject:anObj];
857 [anObj wasLockedInChannel:self context:self->databaseContext];
860 if ([delegate respondsToSelector:@selector(databaseChannel:didLockObject:)])
861 [delegate databaseChannel:self didLockObject:anObj];
862 [self postNotification:EODatabaseChannelDidLockObjectName object:anObj];
866 - (BOOL)refetchObject:(id)anObj {
867 EOEntity *entity = nil;
868 NSDictionary *pkey = nil;
869 NSDictionary *snapshot = nil;
870 EOSQLQualifier *qualifier = nil;
872 // Check the delegate
873 if ([delegate respondsToSelector:
874 @selector(databaseChannel:willRefetchObject:)])
875 anObj = [delegate databaseChannel:self willRefetchObject:anObj];
877 // Check nil (delegate disallowes or given object was nil)
882 if ([EOFault isFault:anObj]) {
883 [NSException raise:NSInvalidArgumentException
884 format:@"Attempt to refetch a fault in a database channel"];
887 // Check if in a transaction
888 if (![databaseContext transactionNestingLevel]) {
889 [self privateReportError:_cmd :
890 @"cannot refetch if context has no transaction opened."];
894 // Check if fetch is in progress
895 if ([self isFetchInProgress]) {
896 [self privateReportError:_cmd :
897 @"cannot refetch if context has a fetch in progress."];
902 entity = [anObj respondsToSelector:@selector(entity)]
904 : [[[[adaptorChannel adaptorContext] adaptor] model] entityForObject:anObj];
908 [self privateReportError:_cmd :
909 @"cannot determine entity for object %p class %s.",
910 anObj, NSStringFromClass([anObj class])];
914 // Get snapshot and old primary key
915 [databaseContext primaryKey:&pkey andSnapshot:&snapshot forObject:anObj];
918 [self privateReportError:_cmd :
919 @"cannot refetch object %p because there is no snapshot for it."];
920 pkey = [entity primaryKeyForRow:snapshot];
923 [self privateReportError:_cmd :
924 @"cannot determine primary key for %p from values %@ entity %@.",
925 anObj, [snapshot description], [entity name]];
929 // Get and check qualifier for object to refetch
930 qualifier = [EOSQLQualifier qualifierForPrimaryKey:pkey entity:entity];
931 if (qualifier == nil) {
932 [self privateReportError:_cmd :
933 @"cannot make qualifier to refetch %p primary key %@ entity %@.",
934 anObj, [pkey description], [entity name]];
938 // Request object from adaptor
939 [self setCurrentEntity:entity];
940 [self privateUpdateCurrentEntityInfo];
941 if (currentAttributes == nil) {
942 [self privateReportError:_cmd :
943 @"internal inconsitency while refetching %p.", anObj];
948 NSAssert3([currentAttributes count] > 0,
949 @"missing attributes for select: lock=%@ object=%@ entity=%@",
950 currentAttributes, anObj, entity);
952 if (![adaptorChannel selectAttributes:currentAttributes
953 describedByQualifier:qualifier
955 lock:([databaseContext updateStrategy] ==
956 EOUpdateWithPessimisticLocking)]) {
957 [self privateClearCurrentEntityInfo];
961 // Get object from adaptor, re-build its faults and record new snapshot
962 anObj = [self privateFetchWithZone:NULL];
965 [self privateReportError:_cmd :
966 @"could not refetch %p with qualifier %@.",
967 anObj, [qualifier description]];
972 if ([delegate respondsToSelector:@selector(databaseChannel:didRefetchObject:)])
973 [delegate databaseChannel:self didRefetchObject:anObj];
977 - (id)_createObjectForRow:(NSDictionary*)aRow entity:(EOEntity*)anEntity
978 isPrimaryKey:(BOOL)yn zone:(NSZone*)zone {
985 class = [self privateClassForEntity:anEntity];
987 // Create new instance
988 if ([class respondsToSelector:@selector(classForEntity:values:)])
989 class = [class classForEntity:anEntity values:aRow];
991 anObj = [class allocWithZone:zone];
996 - (id)allocateObjectForRow:(NSDictionary *)row entity:(EOEntity *)anEntity
997 zone:(NSZone *)zone {
1002 if (anEntity == nil)
1005 class = [self privateClassForEntity:anEntity];
1007 // Create new instance
1008 if ([class respondsToSelector:@selector(classForEntity:values:)])
1009 class = [class classForEntity:anEntity values:row];
1011 anObj = [class allocWithZone:zone];
1016 - (id)initializedObjectForRow:(NSDictionary *)row
1017 entity:(EOEntity *)anEntity
1022 anObj = [self allocateObjectForRow:row entity:anEntity zone:zone];
1024 anObj = [anObj respondsToSelector:@selector(initWithPrimaryKey:entity:)]
1025 ? [anObj initWithPrimaryKey:row entity:anEntity]
1028 return AUTORELEASE(anObj);
1035 - (id)_fetchObject:(id)anObj qualifier:(EOSQLQualifier *)qualifier {
1038 [self selectObjectsDescribedByQualifier:qualifier fetchOrder:nil];
1039 obj = [self fetchWithZone:NULL];
1044 - (BOOL)selectObjectsDescribedByQualifier:(EOSQLQualifier *)qualifier
1045 fetchOrder:(NSArray *)fetchOrder
1047 if ([delegate respondsToSelector:
1048 @selector(databaseChannel:willSelectObjectsDescribedByQualifier:fetchOrder:)])
1049 if (![delegate databaseChannel:self
1050 willSelectObjectsDescribedByQualifier:qualifier
1051 fetchOrder:fetchOrder])
1054 [self setCurrentEntity:[qualifier entity]];
1055 [self privateUpdateCurrentEntityInfo];
1056 if (self->currentAttributes == nil) {
1057 [self privateReportError:_cmd :
1058 @"internal inconsitency while selecting."];
1061 NSAssert3([self->currentAttributes count] > 0,
1062 @"missing select attributes: attrs=%@, qualifier=%@, entity=%@",
1063 self->currentAttributes, qualifier, self->currentEntity);
1065 if (![adaptorChannel selectAttributes:self->currentAttributes
1066 describedByQualifier:qualifier
1067 fetchOrder:fetchOrder
1068 lock:([databaseContext updateStrategy] ==
1069 EOUpdateWithPessimisticLocking)]) {
1070 [self privateClearCurrentEntityInfo];
1071 [self privateReportError:_cmd :
1072 @"could not select attributes with qualifier %@.",
1073 [qualifier description]];
1077 if ([delegate respondsToSelector:
1078 @selector(databaseChannel:didSelectObjectsDescribedByQualifier:fetchOrder:)])
1079 [delegate databaseChannel:self
1080 didSelectObjectsDescribedByQualifier:qualifier
1081 fetchOrder:fetchOrder];
1085 - (id)fetchWithZone:(NSZone *)zone {
1088 if ([delegate respondsToSelector:
1089 @selector(databaseChannel:willFetchObjectOfClass:withZone:)]) {
1092 class = currentClass
1094 : [self privateClassForEntity:currentEntity];
1096 [delegate databaseChannel:self
1097 willFetchObjectOfClass:class
1100 object = [self privateFetchWithZone:zone];
1104 if ([delegate respondsToSelector:@selector(databaseChannel:didFetchObject:)])
1105 [delegate databaseChannel:self didFetchObject:object];
1110 - (BOOL)isFetchInProgress {
1111 return [[self adaptorChannel] isFetchInProgress];
1114 - (void)cancelFetch {
1115 if ([[self adaptorChannel] isFetchInProgress]) {
1116 [self privateClearCurrentEntityInfo];
1117 [[self adaptorChannel] cancelFetch];
1121 - (void)setCurrentEntity:(EOEntity *)_entity {
1122 // Clear entity info
1123 [self privateClearCurrentEntityInfo];
1125 NSAssert(self->currentEntity == nil, @"entity not cleared correctly ..");
1126 self->currentEntity = RETAIN(_entity);
1129 - (void)privateClearCurrentEntityInfo {
1130 RELEASE(self->currentEntity); self->currentEntity = nil;
1131 RELEASE(self->currentAttributes); self->currentAttributes = nil;
1132 RELEASE(self->currentRelations); self->currentRelations = nil;
1133 self->currentClass = Nil;
1134 self->currentReady = NO;
1137 - (void)privateUpdateCurrentEntityInfo {
1138 if (self->currentEntity == nil) {
1139 [NSException raise:NSInvalidArgumentException
1140 format:@"Must use setCurrentEntity if select is not done "
1141 @"through database"];
1144 if (self->currentAttributes == nil)
1145 self->currentAttributes =
1146 RETAIN([self->currentEntity attributesUsedForFetch]);
1147 if (self->currentRelations == nil)
1148 self->currentRelations = RETAIN([self->currentEntity relationsUsedForFetch]);
1149 self->currentReady = YES;
1156 - (Class)privateClassForEntity:(EOEntity *)anEntity {
1159 if (anEntity == currentEntity && currentClass)
1160 return currentClass;
1162 // Get class for new object
1163 class = NSClassFromString([anEntity className]);
1165 if (!class && [delegate respondsToSelector:
1166 @selector(databaseChannel:failedToLookupClassNamed:)])
1167 class = [delegate databaseChannel:self
1168 failedToLookupClassNamed:[[anEntity className] cString]];
1170 class = [EOGenericRecord class];
1172 if (anEntity == currentEntity)
1173 currentClass = class;
1178 - (id)privateFetchWithZone:(NSZone *)_zone {
1179 NSMutableDictionary *values = nil;
1181 NSDictionary *pkey = nil;
1182 NSDictionary *snapshot = nil;
1183 NSDictionary *row = nil;
1184 NSDictionary *dict = nil;;
1186 // Be sure we have entity info (raises if no entity is set)
1187 if (!self->currentReady)
1188 [self privateUpdateCurrentEntityInfo];
1190 // fetch row from adaptor
1191 row = [[self adaptorChannel] fetchAttributes:self->currentAttributes
1194 // Results set finished or no more result sets
1197 row = [row copyWithZone:_zone];
1201 // determine primary key and snapshot
1202 snapshot = [self->currentEntity snapshotForRow:row];
1203 pkey = [self->currentEntity primaryKeyForRow:row];
1205 if ((pkey == nil) || (snapshot == nil)) {
1206 // TODO - should we have a delegate method here ?
1207 [NSException raise:NSInvalidArgumentException
1208 format:@"Cannot determine primary key and snapshot for row"];
1211 // lookup object in context/database
1212 object = [self->databaseContext objectForPrimaryKey:pkey
1213 entity:currentEntity];
1215 // use old, make new, clear fault
1216 if (object == nil) {
1217 //NSLog(@"new anObj\n");
1218 object = [self initializedObjectForRow:row
1219 entity:currentEntity
1222 if ([EOFault isFault:object]) {
1223 [EODatabaseFault clearFault:object];
1225 object = [object respondsToSelector:@selector(initWithPrimaryKey:entity:)]
1226 ? [object initWithPrimaryKey:row entity:currentEntity]
1229 if (object == nil) {
1230 [NSException raise:NSInvalidArgumentException
1231 format:@"could not initialize cleared fault with "
1232 @"row `%@' and entity %@",
1233 [row description], [currentEntity name]];
1238 // TODO - handle only class properties to object
1239 values = [NSMutableDictionary dictionaryWithCapacity:
1240 ([row count] + [currentRelations count])];
1241 [values addEntriesFromDictionary:row];
1243 // resolve relationships (to-one and to-many)
1245 EORelationship *rel = nil;
1246 int i, n = [self->currentRelations count];
1249 for (i = 0; i < n; i++) {
1250 rel = [self->currentRelations objectAtIndex:i];
1252 // Check if the delegate can provide a different relationship
1253 if ([delegate respondsToSelector:
1254 @selector(databaseChannel:relationshipForRow:relationship:)]) {
1255 id nrel = [delegate databaseChannel:self
1256 relationshipForRow:row
1258 rel = nrel ? nrel : rel;
1260 if ([rel isToMany]) {
1261 // Build to-many fault
1262 EOSQLQualifier* qualifier =
1263 [EOSQLQualifier qualifierForRow:row relationship:rel];
1265 if (qualifier == nil) {
1266 // HH: THROW was uncommented ..
1267 [NSException raise:NSInvalidArgumentException
1269 @"Cannot build fault qualifier for relationship"];
1274 #if LIB_FOUNDATION_LIBRARY
1275 if ([NSClassFromString([[rel destinationEntity] className])
1276 isGarbageCollectable])
1277 fault = [EODatabaseFault gcArrayFaultWithQualifier:qualifier
1279 databaseChannel:self
1283 fault = [EODatabaseFault arrayFaultWithQualifier:qualifier
1285 databaseChannel:self
1289 // Build to-one fault
1290 EOEntity *faultEntity;
1291 NSDictionary *faultKey;
1293 faultEntity = [rel destinationEntity];
1294 faultKey = [rel foreignKeyForRow:row];
1295 faultKey = [faultEntity primaryKeyForRow:faultKey];
1297 if (faultEntity == nil) {
1298 [NSException raise:NSInvalidArgumentException
1299 format:@"Cannot get entity for relationship"];
1303 fault = [self->databaseContext objectForPrimaryKey:faultKey
1304 entity:faultEntity];
1306 fault = [EODatabaseFault objectFaultWithPrimaryKey:faultKey
1308 databaseChannel:self
1310 [databaseContext recordObject:fault
1317 fault = [EONull null];
1321 [values setObject:fault forKey:[rel name]];
1325 // check if is updated in another context or just updated or new (delegate)
1327 if ([[databaseContext database] isObject:object
1328 updatedOutsideContext:databaseContext]) {
1329 if ([delegate respondsToSelector:
1330 @selector(databaseChannel:willRefetchConflictingObject:withSnapshot:)]) {
1331 dict = [delegate databaseChannel:self
1332 willRefetchConflictingObject:object
1333 withSnapshot:values];
1336 [NSException raise:NSInvalidArgumentException
1337 format:@"object updated in an uncommitted transaction "
1342 if ([delegate respondsToSelector:
1343 @selector(databaseChannel:willRefetchObject:fromSnapshot:)]) {
1344 dict = [delegate databaseChannel:self
1345 willRefetchObject:object
1346 fromSnapshot:values];
1349 // does delegate disallow setting the new values and recording the fetch ?
1354 [object takeValuesFromDictionary:dict];
1356 // register lock if locked
1357 if ([databaseContext updateStrategy] == EOUpdateWithPessimisticLocking)
1358 [databaseContext recordLockedObject:object];
1360 // register object in context
1361 [databaseContext recordObject:object
1363 entity:currentEntity
1366 // awake object from database channel
1367 if ([object respondsToSelector:@selector(awakeForDatabaseChannel:)])
1368 [object awakeForDatabaseChannel:self];
1374 // ******************** Reporting errors ********************
1376 - (void)privateReportError:(SEL)method :(NSString*)format,... {
1380 if (![[databaseContext database] logsErrorMessages])
1383 va_start(va, format);
1384 message = AUTORELEASE([[NSString alloc] initWithFormat:format arguments:va]);
1387 [[databaseContext database]
1389 @"EODatabaseChannel:error in [EODatabaseChannel %@]: %@",
1390 NSStringFromSelector(method), message];
1393 @end /* EODatabaseChannel */
1395 @implementation NSObject(EODatabaseChannelEONotifications)
1397 - (BOOL)prepareForDeleteInChannel:(EODatabaseChannel *)_channel
1398 context:(EODatabaseContext *)_ctx
1402 - (void)wasDeletedInChannel:(EODatabaseChannel *)_channel
1403 context:(EODatabaseContext *)_ctx
1407 - (BOOL)prepareForInsertInChannel:(EODatabaseChannel *)_channel
1408 context:(EODatabaseContext *)_ctx
1412 - (void)wasInsertedInChannel:(EODatabaseChannel *)_channel
1413 context:(EODatabaseContext *)_ctx
1417 - (BOOL)prepareForUpdateInChannel:(EODatabaseChannel *)_channel
1418 context:(EODatabaseContext *)_ctx
1422 - (void)wasUpdatedInChannel:(EODatabaseChannel *)_channel
1423 context:(EODatabaseContext *)_ctx
1427 - (BOOL)prepareForLockInChannel:(EODatabaseChannel *)_channel
1428 context:(EODatabaseContext *)_ctx
1432 - (void)wasLockedInChannel:(EODatabaseChannel *)_channel
1433 context:(EODatabaseContext *)_ctx
1437 @end /* NSObject(EODatabaseChannelNotifications) */
1439 @implementation EODatabaseChannel(Statistics)
1441 - (unsigned int)successfulOpenCount {
1442 return self->successfulOpenCount;
1445 - (unsigned int)failedOpenCount {
1446 return self->failedOpenCount;
1449 - (unsigned int)closeCount {
1450 return self->closeCount;
1453 - (unsigned int)insertCount {
1454 return self->insertCount;
1457 - (unsigned int)updateCount {
1458 return self->updateCount;
1461 - (unsigned int)deleteCount {
1462 return self->deleteCount;
1465 - (unsigned int)lockCount {
1466 return self->lockCount;