4 Copyright (C) 1996 Free Software Foundation, Inc.
6 Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>
9 Author: Helge Hess <helge.hess@mdlink.de>
12 This file is part of the GNUstep Database Library.
14 This library is free software; you can redistribute it and/or
15 modify it under the terms of the GNU Library General Public
16 License as published by the Free Software Foundation; either
17 version 2 of the License, or (at your option) any later version.
19 This library is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 Library General Public License for more details.
24 You should have received a copy of the GNU Library General Public
25 License along with this library; see the file COPYING.LIB.
26 If not, write to the Free Software Foundation,
27 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31 #import "EODatabaseContext.h"
33 #import "EOAdaptorContext.h"
34 #import "EODatabase.h"
35 #import "EODatabaseChannel.h"
37 #import "EODatabaseFault.h"
38 #import "EOGenericRecord.h"
40 #import "EOObjectUniquer.h"
41 #include "EOModelGroup.h"
42 #include <EOControl/EOFetchSpecification.h>
43 #include <EOControl/EOKeyGlobalID.h>
45 NSString *EODatabaseContextWillBeginTransactionName =
46 @"EODatabaseContextWillBeginTransaction";
47 NSString *EODatabaseContextDidBeginTransactionName =
48 @"EODatabaseContextDidBeginTransaction";
49 NSString *EODatabaseContextWillRollbackTransactionName =
50 @"EODatabaseContextWillRollbackTransaction";
51 NSString *EODatabaseContextDidRollbackTransactionName =
52 @"EODatabaseContextDidRollbackTransaction";
53 NSString *EODatabaseContextWillCommitTransactionName =
54 @"EODatabaseContextWillCommitTransaction";
55 NSString *EODatabaseContextDidCommitTransactionName =
56 @"EODatabaseContextDidCommitTransaction";
58 struct EODatabaseContextModificationQueue {
59 struct EODatabaseContextModificationQueue *next;
72 typedef struct _EOTransactionScope {
73 struct _EOTransactionScope *previous;
74 EOObjectUniquer *objectsDictionary;
75 NSMutableArray *objectsUpdated;
76 NSMutableArray *objectsDeleted;
77 NSMutableArray *objectsLocked;
80 static inline EOTransactionScope *_newTxScope(NSZone *_zone) {
81 EOTransactionScope *newScope;
83 newScope = NSZoneMalloc(_zone, sizeof(EOTransactionScope));
84 newScope->objectsDictionary = [[EOObjectUniquer allocWithZone:_zone] init];
85 newScope->objectsUpdated = [[NSMutableArray allocWithZone:_zone] init];
86 newScope->objectsDeleted = [[NSMutableArray allocWithZone:_zone] init];
87 newScope->objectsLocked = [[NSMutableArray allocWithZone:_zone] init];
91 static inline void _freeTxScope(NSZone *_zone, EOTransactionScope *_txScope) {
92 RELEASE(_txScope->objectsDictionary); _txScope->objectsDictionary = nil;
93 RELEASE(_txScope->objectsUpdated); _txScope->objectsUpdated = nil;
94 RELEASE(_txScope->objectsDeleted); _txScope->objectsDeleted = nil;
95 RELEASE(_txScope->objectsLocked); _txScope->objectsLocked = nil;
96 NSZoneFree(_zone, _txScope); _txScope = NULL;
99 @implementation EODatabaseContext
101 #if 0 // no such callback!
103 static BOOL isInitialized = NO;
104 if (!isInitialized) {
106 [[NSNotificationCenter defaultCenter]
108 selector:@selector(_objectStoreNeeded:)
109 name:@"EOCooperatingObjectStoreNeeded"
115 static inline void _checkTxInProgress(EODatabaseContext *self,
116 const char *_function)
118 if (self->transactionNestingLevel == 0) {
119 [NSException raise:NSInternalInconsistencyException
121 @"EODatabaseContext:%x: No transaction in progress "
122 @"in %s", self, _function];
128 - (id)initWithDatabase:(EODatabase *)aDatabase {
129 static int reuseAdaptorCtx = -1;
130 if (reuseAdaptorCtx == -1) {
131 reuseAdaptorCtx = [[[NSUserDefaults standardUserDefaults]
132 objectForKey:@"EOReuseAdaptorContext"]
135 if (reuseAdaptorCtx) {
136 NSEnumerator *contexts;
137 EOAdaptorContext *actx;
139 contexts = [[[aDatabase adaptor] contexts] objectEnumerator];
140 while ((actx = [contexts nextObject])) {
141 if (![actx hasOpenTransaction]) {
143 NSLog(@"reuse adaptor context: %@", actx);
145 self->adaptorContext = actx;
149 if (self->adaptorContext == nil)
150 self->adaptorContext = [[aDatabase adaptor] createAdaptorContext];
153 self->adaptorContext = [[aDatabase adaptor] createAdaptorContext];
155 if ((aDatabase == nil) || (adaptorContext == nil)) {
156 NSLog(@"EODatabaseContext could not create adaptor context");
160 RETAIN(self->adaptorContext);
162 self->database = RETAIN(aDatabase);
163 self->channels = [[NSMutableArray allocWithZone:[self zone]] init];
164 self->transactionStackTop = NULL;
165 self->transactionNestingLevel = 0;
166 self->updateStrategy = EOUpdateWithOptimisticLocking;
167 self->isKeepingSnapshots = YES;
168 self->isUniquingObjects = [self->database uniquesObjects];
170 [database contextDidInit:self];
175 [database contextWillDealloc:self];
178 struct EODatabaseContextModificationQueue *q;
180 while ((q = self->ops)) {
187 while (self->transactionNestingLevel) {
188 if (![self rollbackTransaction])
191 while (self->transactionStackTop)
192 [self privateRollbackTransaction];
194 RELEASE(self->adaptorContext); self->adaptorContext = nil;
195 RELEASE(self->database); self->database = nil;
196 RELEASE(self->channels); self->channels = nil;
202 - (void)setDelegate:(id)_delegate {
203 self->delegate = _delegate;
206 return self->delegate;
209 - (EODatabase *)database {
210 return self->database;
213 - (EOAdaptorContext *)adaptorContext {
214 return self->adaptorContext;
219 - (BOOL)hasBusyChannels {
222 for (i = [channels count]-1; i >= 0; i--) {
223 if ([[[channels objectAtIndex:i] nonretainedObjectValue]
230 - (BOOL)hasOpenChannels {
233 for (i = [channels count]-1; i >= 0; i--) {
234 if ([[[channels objectAtIndex:i] nonretainedObjectValue] isOpen])
240 - (NSArray *)channels {
241 return [self registeredChannels];
244 - (id)createChannel {
245 return AUTORELEASE([[EODatabaseChannel alloc] initWithDatabaseContext:self]);
248 - (void)channelDidInit:(id)aChannel {
249 [self registerChannel:aChannel];
252 - (void)channelWillDealloc:(id)aChannel {
253 [self unregisterChannel:aChannel];
257 * Controlling transactions
260 - (BOOL)beginTransaction {
261 NSNotificationCenter *nc;
263 if ([adaptorContext transactionNestingLevel] !=
264 (unsigned)transactionNestingLevel) {
265 [NSException raise:NSInternalInconsistencyException
267 @"EODatabaseContext:%x:transaction nesting levels do not match: "
268 @"database has %d, adaptor has %d, "
269 @"in [EODatabaseContext beginTransaction]",
270 self, transactionNestingLevel,
271 [adaptorContext transactionNestingLevel]];
274 nc = [NSNotificationCenter defaultCenter];
276 [nc postNotificationName:EODatabaseContextWillBeginTransactionName
279 if (![self->adaptorContext beginTransaction])
281 [self privateBeginTransaction];
284 [nc postNotificationName:EODatabaseContextDidBeginTransactionName
289 - (BOOL)commitTransaction {
290 NSNotificationCenter *nc;
292 _checkTxInProgress(self, __PRETTY_FUNCTION__);
294 if ([adaptorContext transactionNestingLevel] !=
295 (unsigned)self->transactionNestingLevel) {
296 [NSException raise:NSInternalInconsistencyException
298 @"EODatabaseContext:%x:transaction nesting levels do not match: "
299 @"database has %d, adaptor has %d, "
300 @"in [EODatabaseContext commitTransaction]",
301 self, transactionNestingLevel,
302 [adaptorContext transactionNestingLevel]];
305 nc = [NSNotificationCenter defaultCenter];
306 [nc postNotificationName:EODatabaseContextWillCommitTransactionName
309 if (![adaptorContext commitTransaction])
311 [self privateCommitTransaction];
313 self->txCommitCount++;
314 [nc postNotificationName:EODatabaseContextDidCommitTransactionName
319 - (BOOL)rollbackTransaction {
320 NSNotificationCenter *nc;
322 _checkTxInProgress(self, __PRETTY_FUNCTION__);
324 if ([self->adaptorContext transactionNestingLevel] !=
325 (unsigned)self->transactionNestingLevel) {
326 [NSException raise:NSInternalInconsistencyException
328 @"EODatabaseContext:%x:transaction nesting levels do not match: "
329 @"database has %d, adaptor has %d, "
330 @"in [EODatabaseContext rollbackTransaction]",
331 self, transactionNestingLevel,
332 [adaptorContext transactionNestingLevel]];
335 nc = [NSNotificationCenter defaultCenter];
336 [nc postNotificationName:EODatabaseContextWillRollbackTransactionName
339 if (![self->adaptorContext rollbackTransaction])
341 [self privateRollbackTransaction];
343 self->txRollbackCount++;
344 [nc postNotificationName:EODatabaseContextDidRollbackTransactionName
350 // ******************** notifications ********************
352 - (void)transactionDidBegin {
353 [self->adaptorContext transactionDidBegin];
354 [self privateBeginTransaction];
357 - (void)transactionDidCommit {
358 _checkTxInProgress(self, __PRETTY_FUNCTION__);
359 [self->adaptorContext transactionDidCommit];
360 [self privateCommitTransaction];
363 - (void)transactionDidRollback {
364 _checkTxInProgress(self, __PRETTY_FUNCTION__);
365 [adaptorContext transactionDidRollback];
366 [self privateRollbackTransaction];
370 * Nesting transactions
373 - (BOOL)canNestTransactions {
374 return [adaptorContext canNestTransactions];
376 - (unsigned)transactionNestingLevel {
377 return transactionNestingLevel;
381 * Setting the update strategy
384 - (void)setUpdateStrategy:(EOUpdateStrategy)aStrategy {
385 if ([self transactionNestingLevel]) {
386 [NSException raise:NSInvalidArgumentException
388 @"EODatabaseContext:%x: Cannot change update strategy "
389 @"when context has a transaction open, "
390 @"in [EODatabaseContext setUpdateStrategy]",
393 updateStrategy = aStrategy;
394 isKeepingSnapshots = (updateStrategy == EOUpdateWithNoLocking) ? NO : YES;
395 isUniquingObjects = [database uniquesObjects];
398 - (EOUpdateStrategy)updateStrategy {
399 return self->updateStrategy;
402 - (BOOL)keepsSnapshots {
403 return self->isKeepingSnapshots;
407 * Processing transactions internally
410 - (void)privateBeginTransaction {
411 EOTransactionScope *newScope = NULL;
413 newScope = _newTxScope([self zone]);
414 newScope->previous = transactionNestingLevel ? transactionStackTop : NULL;
415 transactionStackTop = newScope;
416 transactionNestingLevel++;
418 if (transactionNestingLevel == 1)
419 self->isUniquingObjects = [database uniquesObjects];
422 - (void)privateCommitTransaction {
423 EOTransactionScope *newScope = transactionStackTop;
425 transactionStackTop = newScope->previous;
426 transactionNestingLevel--;
428 // In nested transaction fold updated and deleted objects
429 // into the parent transaction; locked objects are forgotten
430 // deleted objects are deleted form the parent transaction
431 if (transactionNestingLevel) {
432 // Keep updated objects
433 [transactionStackTop->objectsUpdated
434 addObjectsFromArray:newScope->objectsUpdated];
435 // Keep deleted objects
436 [transactionStackTop->objectsDeleted
437 addObjectsFromArray:newScope->objectsDeleted];
438 // Register objects in parent transaction scope
439 [newScope->objectsDictionary
440 transferTo:transactionStackTop->objectsDictionary
441 objects:YES andSnapshots:YES];
443 // If this was the first transaction then fold the changes
444 // into the database; locked and updateted objects are forgotten
448 for (i = 0, n = [newScope->objectsDeleted count]; i < n; i++)
449 [database forgetObject:[newScope->objectsDeleted objectAtIndex:i]];
451 // Register objects into the database
452 if (self->isUniquingObjects || [database keepsSnapshots]) {
453 [newScope->objectsDictionary transferTo:[database objectUniquer]
454 objects:self->isUniquingObjects
455 andSnapshots:[database keepsSnapshots]];
459 // Kill transaction scope
460 _freeTxScope([self zone], newScope);
463 - (void)privateRollbackTransaction {
464 EOTransactionScope *newScope = transactionStackTop;
466 transactionStackTop = newScope->previous;
467 transactionNestingLevel--;
469 // Forget snapshots, updated, deleted and locked objects
470 // in current transaction
472 // Kill transaction scope
473 _freeTxScope([self zone], newScope);
478 - (void)forgetObject:(id)_object {
479 EOTransactionScope *scope = NULL;
481 _checkTxInProgress(self, __PRETTY_FUNCTION__);
483 if (_object == nil) {
484 [NSException raise:NSInvalidArgumentException
486 @"EODatabaseContext:%x: Cannot forget null object, "
487 @"in [EODatabaseContext forgetObject]",
490 if ([EOFault isFault:_object]) {
491 [NSException raise:NSInvalidArgumentException
493 @"EODatabaseContext:%x: Cannot forget forget a fault object, "
494 @"in [EODatabaseContext forgetObject]",
498 [transactionStackTop->objectsDeleted addObject:_object];
500 for (scope = transactionStackTop; scope; scope = scope->previous) {
501 [scope->objectsDictionary forgetObject:_object];
505 - (id)objectForPrimaryKey:(NSDictionary *)_key entity:(EOEntity *)_entity {
506 EOTransactionScope *scope = NULL;
509 if (!self->isUniquingObjects || (_key == nil) || (_entity == nil))
512 _key = [_entity primaryKeyForRow:_key];
513 if (_key == nil) return nil;
515 for (scope = transactionStackTop; scope; scope = scope->previous) {
516 _object = [scope->objectsDictionary objectForPrimaryKey:_key
522 return [self->database objectForPrimaryKey:_key entity:_entity];
525 - (void)recordObject:(id)_object
526 primaryKey:(NSDictionary *)_key
527 entity:(EOEntity *)_entity
528 snapshot:(NSDictionary *)snapshot
530 _checkTxInProgress(self, __PRETTY_FUNCTION__);
532 if (_object == nil) {
533 [NSException raise:NSInvalidArgumentException
535 @"EODatabaseContext:%x: Cannot record null object, "
536 @"in [EODatabaseContext recordObject:primaryKey:entity:snapshot:]",
539 if ((_entity == nil) && self->isUniquingObjects) {
540 [NSException raise:NSInvalidArgumentException
542 @"EODatabaseContext:%x: Cannot record object with null entity "
543 @"when uniquing objects, "
544 @"in [EODatabaseContext recordObject:primaryKey:entity:snapshot:]",
548 _key = [_entity primaryKeyForRow:_key];
550 if ((_key == nil) && self->isUniquingObjects) {
551 [NSException raise:NSInvalidArgumentException
553 @"EODatabaseContext:%x: Cannot record object with null key "
554 @"when uniquing objects, "
555 @"in [EODatabaseContext recordObject:primaryKey:entity:snapshot:]",
558 if ((snapshot == nil) && isKeepingSnapshots && ![EOFault isFault:_object]) {
559 [NSException raise:NSInvalidArgumentException
561 @"EODatabaseContext:%x: Cannot record object with null snapshot "
562 @"when keeping snapshots, "
563 @"in [EODatabaseContext recordObject:primaryKey:entity:snapshot:]"
564 @": snapshot=%s keepsSnapshots=%s isFault=%s",
566 snapshot ? "yes" : "no",
567 isKeepingSnapshots ? "yes" : "no",
568 [EOFault isFault:_object] ? "yes" : "no"];
571 if (self->isKeepingSnapshots || self->isUniquingObjects) {
572 EOObjectUniquer *cache = transactionStackTop->objectsDictionary;
574 [cache recordObject:_object
575 primaryKey: self->isUniquingObjects ? _key : nil
576 entity: self->isUniquingObjects ? _entity : nil
577 snapshot: self->isKeepingSnapshots ? snapshot : nil];
581 - (void)recordObject:(id)_object
582 primaryKey:(NSDictionary *)_key
583 snapshot:(NSDictionary *)_snapshot
585 EOEntity *entity = nil;
587 entity = [_object respondsToSelector:@selector(entity)]
589 : [[[database adaptor] model] entityForObject:_object];
591 [self recordObject:_object primaryKey:_key entity:entity snapshot:_snapshot];
594 - (NSDictionary *)snapshotForObject:(id)_object {
595 EOTransactionScope *scope = NULL;
596 EOUniquerRecord *rec = NULL;
598 if (!isKeepingSnapshots)
601 for (scope = transactionStackTop; scope; scope = scope->previous) {
602 rec = [scope->objectsDictionary recordForObject:_object];
604 return rec->snapshot;
607 rec = [[self->database objectUniquer] recordForObject:_object];
608 if (rec) return rec->snapshot;
613 - (NSDictionary*)primaryKeyForObject:(id)_object {
614 EOTransactionScope *scope = NULL;
615 EOUniquerRecord *rec = NULL;
617 if ([self->database uniquesObjects])
620 for (scope = transactionStackTop; scope; scope = scope->previous) {
621 rec = [scope->objectsDictionary recordForObject:_object];
622 if (rec) return rec->pkey;
625 rec = [[self->database objectUniquer] recordForObject:_object];
626 if (rec) return rec->pkey;
630 - (void)primaryKey:(NSDictionary **)_key
631 andSnapshot:(NSDictionary **)_snapshot
634 EOTransactionScope *scope = NULL;
635 EOUniquerRecord *rec = NULL;
637 if (!self->isKeepingSnapshots && ![self->database uniquesObjects]) {
638 *_key = *_snapshot = nil;
642 for (scope = transactionStackTop; scope; scope = scope->previous) {
643 rec = [scope->objectsDictionary recordForObject:_object];
645 if (_key) *_key = rec->pkey;
646 if (_snapshot) *_snapshot = rec->snapshot;
651 rec = [[self->database objectUniquer] recordForObject:_object];
653 if (_key) *_key = rec->pkey;
654 if (_snapshot) *_snapshot = rec->snapshot;
658 if (_key) *_key = nil;
659 if (_snapshot) *_snapshot = nil;
662 - (void)recordLockedObject:(id)_object {
663 _checkTxInProgress(self, __PRETTY_FUNCTION__);
665 if (_object == nil) {
666 [NSException raise:NSInvalidArgumentException
668 @"EODatabaseContext:%x: Cannot record null object as locked, "
669 @"in [EODatabaseContext recordLockedObject:]",
672 if ([EOFault isFault:_object]) {
673 [NSException raise:NSInvalidArgumentException
675 @"EODatabaseContext:%x: Cannot record a fault object as locked, "
676 @"in [EODatabaseContext recordLockedObject:]",
679 [transactionStackTop->objectsLocked addObject:_object];
682 - (BOOL)isObjectLocked:(id)_object {
683 EOTransactionScope *scope;
685 for (scope = transactionStackTop; scope; scope = scope->previous) {
686 if ([scope->objectsLocked indexOfObjectIdenticalTo:_object]!=NSNotFound)
692 - (void)recordUpdatedObject:(id)_object {
693 _checkTxInProgress(self, __PRETTY_FUNCTION__);
695 if (_object == nil) {
696 [NSException raise:NSInvalidArgumentException
698 @"EODatabaseContext:%x: Cannot record null object as updatetd, "
699 @"in [EODatabaseContext recordUpdatedObject:]",
702 if ([EOFault isFault:_object]) {
703 [NSException raise:NSInvalidArgumentException
705 @"EODatabaseContext:%x: Cannot record fault object as updated, "
706 @"in [EODatabaseContext recordUpdatedObject:]",
710 [transactionStackTop->objectsUpdated addObject:_object];
713 - (BOOL)isObjectUpdated:(id)_object {
714 EOTransactionScope *scope;
716 for (scope = transactionStackTop; scope; scope = scope->previous) {
717 if ([scope->objectsUpdated indexOfObjectIdenticalTo:_object] != NSNotFound)
719 if ([scope->objectsDeleted indexOfObjectIdenticalTo:_object] != NSNotFound)
727 - (NSString *)description {
728 return [NSString stringWithFormat:
729 @"<%@[0x%08X]: #channels=%i tx-nesting=%i>",
730 NSStringFromClass([self class]), self,
731 [self->channels count],
732 [self transactionNestingLevel]];
735 @end /* EODatabaseContext */
737 @implementation EODatabaseContext(Statistics)
739 - (unsigned int)transactionBeginCount {
740 return self->txBeginCount;
742 - (unsigned int)transactionCommitCount {
743 return self->txCommitCount;
745 - (unsigned int)transactionRollbackCount {
746 return self->txRollbackCount;
749 @end /* EODatabaseContext(Statistics) */
751 @implementation EODatabaseContext(NewInEOF2)
754 static Class EODatabaseContextClass = Nil;
756 + (void)setContextClassToRegister:(Class)_cclass {
757 EODatabaseContextClass = _cclass;
759 + (Class)contextClassToRegister {
760 return EODatabaseContextClass ? EODatabaseContextClass : self;
763 - (EODatabaseChannel *)availableChannel {
766 for (i = [self->channels count] - 1; i >= 0; i--) {
767 EODatabaseChannel *channel;
769 channel = [[self->channels objectAtIndex:i] nonretainedObjectValue];
770 if (![channel isFetchInProgress])
774 [[NSNotificationCenter defaultCenter]
775 postNotificationName:@"EODatabaseChannelNeeded"
778 /* recheck for channel */
780 for (i = [self->channels count] - 1; i >= 0; i--) {
781 EODatabaseChannel *channel;
783 channel = [[self->channels objectAtIndex:i] nonretainedObjectValue];
784 if (![channel isFetchInProgress])
791 - (NSArray *)registeredChannels {
792 NSMutableArray *array;
795 array = [NSMutableArray array];
796 for (i=0, n=[channels count]; i < n; i++) {
797 EODatabaseChannel *channel =
798 [[channels objectAtIndex:i] nonretainedObjectValue];
800 [array addObject:channel];
805 - (void)registerChannel:(EODatabaseChannel *)_channel {
806 [self->channels addObject:[NSValue valueWithNonretainedObject:_channel]];
808 - (void)unregisterChannel:(EODatabaseChannel *)_channel {
811 for (i = [self->channels count] - 1; i >= 0; i--) {
812 EODatabaseChannel *channel;
814 channel = [[self->channels objectAtIndex:i] nonretainedObjectValue];
816 if (channel == _channel) {
817 [channels removeObjectAtIndex:i];
823 /* cooperating object store */
825 - (void)commitChanges {
826 [self commitTransaction];
828 - (void)rollbackChanges {
829 [self rollbackTransaction];
832 - (void)performChanges {
833 [self notImplemented:_cmd];
836 /* store specific properties */
838 - (NSDictionary *)valuesForKeys:(NSArray *)_keys object:(id)_object {
839 return [_object valuesForKeys:_keys];
844 - (BOOL)handlesFetchSpecification:(EOFetchSpecification *)_fspec {
847 entity = [[self database] entityNamed:[_fspec entityName]];
848 return entity ? YES : NO;
853 - (BOOL)ownsObject:(id)_object {
856 entity = [[self database] entityNamed:[_object entityName]];
857 return entity ? YES : NO;
860 - (BOOL)ownsGlobalID:(EOGlobalID *)_oid {
863 if (![_oid respondsToSelector:@selector(entityName)])
866 entity = [[self database] entityNamed:[(id)_oid entityName]];
867 return entity ? YES : NO;
870 @end /* EODatabaseContext(NewInEOF2) */