]> err.no Git - sope/blob - sope-gdl1/GDLAccess/EODatabaseContext.m
fixed bug in GDL adaptor lookup
[sope] / sope-gdl1 / GDLAccess / EODatabaseContext.m
1 /* 
2    EODatabaseContext.m
3
4    Copyright (C) 1996 Free Software Foundation, Inc.
5
6    Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>
7    Date: 1996
8    
9    Author: Helge Hess <helge.hess@mdlink.de>
10    Date: 1999
11    
12    This file is part of the GNUstep Database Library.
13
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.
18
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.
23
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.
28 */
29
30 #import "common.h"
31 #import "EODatabaseContext.h"
32 #import "EOAdaptor.h"
33 #import "EOAdaptorContext.h"
34 #import "EODatabase.h"
35 #import "EODatabaseChannel.h"
36 #import "EOEntity.h"
37 #import "EODatabaseFault.h"
38 #import "EOGenericRecord.h"
39 #import "EOModel.h"
40 #import "EOObjectUniquer.h"
41 #include "EOModelGroup.h"
42 #include <EOControl/EOFetchSpecification.h>
43 #include <EOControl/EOKeyGlobalID.h>
44
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";
57
58 struct EODatabaseContextModificationQueue {
59   struct EODatabaseContextModificationQueue *next;
60   enum {
61     update,
62     delete,
63     insert
64   } op;
65   id object;
66 };
67
68 /*
69  * Transaction scope
70  */
71
72 typedef struct _EOTransactionScope {
73   struct _EOTransactionScope *previous;
74   EOObjectUniquer            *objectsDictionary;
75   NSMutableArray             *objectsUpdated;
76   NSMutableArray             *objectsDeleted;
77   NSMutableArray             *objectsLocked;
78 } EOTransactionScope;
79
80 static inline EOTransactionScope *_newTxScope(NSZone *_zone) {
81   EOTransactionScope *newScope;
82
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];
88
89   return newScope;
90 }
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;
97 }
98
99 @implementation EODatabaseContext
100
101 #if 0 // no such callback!
102 + (void)initialize {
103   static BOOL isInitialized = NO;
104   if (!isInitialized) {
105     isInitialized = YES;
106     [[NSNotificationCenter defaultCenter]
107                            addObserver:self
108                            selector:@selector(_objectStoreNeeded:)
109                            name:@"EOCooperatingObjectStoreNeeded"
110                            object:nil];
111   }
112 }
113 #endif
114
115 static inline void _checkTxInProgress(EODatabaseContext *self,
116                                       const char *_function)
117 {
118   if (self->transactionNestingLevel == 0) {
119     [NSException raise:NSInternalInconsistencyException
120                  format:
121               @"EODatabaseContext:%x: No transaction in progress "
122               @"in %s", self, _function];
123   }
124 }
125
126 // init
127
128 - (id)initWithDatabase:(EODatabase *)aDatabase {
129   static int reuseAdaptorCtx = -1;
130   if (reuseAdaptorCtx == -1) {
131     reuseAdaptorCtx = [[[NSUserDefaults standardUserDefaults]
132                                         objectForKey:@"EOReuseAdaptorContext"]
133                                         boolValue] ? 1 : 0;
134   }
135   if (reuseAdaptorCtx) {
136     NSEnumerator     *contexts;
137     EOAdaptorContext *actx;
138     
139     contexts = [[[aDatabase adaptor] contexts] objectEnumerator];
140     while ((actx = [contexts nextObject])) {
141       if (![actx hasOpenTransaction]) {
142 #if DEBUG
143         NSLog(@"reuse adaptor context: %@", actx);
144 #endif
145         self->adaptorContext = actx;
146         break;
147       }
148     }
149     if (self->adaptorContext == nil)
150       self->adaptorContext = [[aDatabase adaptor] createAdaptorContext];
151   }
152   else
153     self->adaptorContext = [[aDatabase adaptor] createAdaptorContext];
154   
155   if ((aDatabase == nil) || (adaptorContext == nil)) {
156     NSLog(@"EODatabaseContext could not create adaptor context");
157     AUTORELEASE(self);
158     return nil;
159   }
160   RETAIN(self->adaptorContext);
161   
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];
169   
170   [database contextDidInit:self];
171   return self;
172 }
173
174 - (void)dealloc {
175   [database contextWillDealloc:self];
176
177   if (self->ops) {
178     struct EODatabaseContextModificationQueue *q;
179
180     while ((q = self->ops)) {
181       self->ops = q->next;
182       RELEASE(q->object);
183       free(q);
184     }
185   }
186   
187   while (self->transactionNestingLevel) {
188     if (![self rollbackTransaction])
189       break;
190   }
191   while (self->transactionStackTop)
192     [self privateRollbackTransaction];
193
194   RELEASE(self->adaptorContext); self->adaptorContext = nil;
195   RELEASE(self->database);       self->database       = nil;
196   RELEASE(self->channels);       self->channels       = nil;
197   [super dealloc];
198 }
199
200 /* accessors */
201
202 - (void)setDelegate:(id)_delegate {
203   self->delegate = _delegate;
204 }
205 - (id)delegate {
206   return self->delegate;
207 }
208
209 - (EODatabase *)database {
210   return self->database;
211 }
212
213 - (EOAdaptorContext *)adaptorContext {
214   return self->adaptorContext;
215 }
216
217 // channels
218
219 - (BOOL)hasBusyChannels {
220   int i;
221     
222   for (i = [channels count]-1; i >= 0; i--) {
223     if ([[[channels objectAtIndex:i] nonretainedObjectValue]
224                     isFetchInProgress])
225       return YES;
226   }
227   return NO;
228 }
229
230 - (BOOL)hasOpenChannels {
231   int i;
232     
233   for (i = [channels count]-1; i >= 0; i--) {
234     if ([[[channels objectAtIndex:i] nonretainedObjectValue] isOpen])
235       return YES;
236   }
237   return NO;
238 }
239
240 - (NSArray *)channels {
241   return [self registeredChannels];
242 }
243
244 - (id)createChannel {
245   return AUTORELEASE([[EODatabaseChannel alloc] initWithDatabaseContext:self]);
246 }
247
248 - (void)channelDidInit:(id)aChannel {
249   [self registerChannel:aChannel];
250 }
251
252 - (void)channelWillDealloc:(id)aChannel {
253   [self unregisterChannel:aChannel];
254 }
255
256 /*
257  * Controlling transactions
258  */
259
260 - (BOOL)beginTransaction {
261   NSNotificationCenter *nc;
262   
263   if ([adaptorContext transactionNestingLevel] != 
264       (unsigned)transactionNestingLevel) {
265     [NSException raise:NSInternalInconsistencyException
266                  format:
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]];
272   }
273
274   nc = [NSNotificationCenter defaultCenter];
275
276   [nc postNotificationName:EODatabaseContextWillBeginTransactionName
277       object:self];
278   
279   if (![self->adaptorContext beginTransaction])
280     return NO;
281   [self privateBeginTransaction];
282
283   txBeginCount++;
284   [nc postNotificationName:EODatabaseContextDidBeginTransactionName
285       object:self];
286   return YES;
287 }
288
289 - (BOOL)commitTransaction {
290   NSNotificationCenter *nc;
291   
292   _checkTxInProgress(self, __PRETTY_FUNCTION__);
293
294   if ([adaptorContext transactionNestingLevel] !=
295       (unsigned)self->transactionNestingLevel) {
296     [NSException raise:NSInternalInconsistencyException
297                  format:
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]];
303   }
304
305   nc = [NSNotificationCenter defaultCenter];
306   [nc postNotificationName:EODatabaseContextWillCommitTransactionName
307       object:self];
308   
309   if (![adaptorContext commitTransaction])
310     return NO;
311   [self privateCommitTransaction];
312   
313   self->txCommitCount++;
314   [nc postNotificationName:EODatabaseContextDidCommitTransactionName
315       object:self];
316   return YES;
317 }
318
319 - (BOOL)rollbackTransaction {
320   NSNotificationCenter *nc;
321   
322   _checkTxInProgress(self, __PRETTY_FUNCTION__);
323
324   if ([self->adaptorContext transactionNestingLevel] !=
325       (unsigned)self->transactionNestingLevel) {
326     [NSException raise:NSInternalInconsistencyException
327                  format:
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]];
333   }
334
335   nc = [NSNotificationCenter defaultCenter];
336   [nc postNotificationName:EODatabaseContextWillRollbackTransactionName
337       object:self];
338   
339   if (![self->adaptorContext rollbackTransaction])
340     return NO;
341   [self privateRollbackTransaction];
342   
343   self->txRollbackCount++;
344   [nc postNotificationName:EODatabaseContextDidRollbackTransactionName
345       object:self];
346   return YES;
347 }
348
349
350 // ******************** notifications ********************
351
352 - (void)transactionDidBegin {
353   [self->adaptorContext transactionDidBegin];
354   [self privateBeginTransaction];
355 }
356
357 - (void)transactionDidCommit {
358   _checkTxInProgress(self, __PRETTY_FUNCTION__);
359   [self->adaptorContext transactionDidCommit];
360   [self privateCommitTransaction];
361 }
362
363 - (void)transactionDidRollback {
364   _checkTxInProgress(self, __PRETTY_FUNCTION__);
365   [adaptorContext transactionDidRollback];
366   [self privateRollbackTransaction];
367 }
368
369 /*
370  * Nesting transactions
371  */
372
373 - (BOOL)canNestTransactions {
374   return [adaptorContext canNestTransactions];
375 }
376 - (unsigned)transactionNestingLevel {
377   return transactionNestingLevel;
378 }
379
380 /*
381  * Setting the update strategy
382  */
383
384 - (void)setUpdateStrategy:(EOUpdateStrategy)aStrategy {
385   if ([self transactionNestingLevel]) {
386     [NSException raise:NSInvalidArgumentException
387                  format:
388         @"EODatabaseContext:%x: Cannot change update strategy "
389         @"when context has a transaction open, "
390         @"in [EODatabaseContext setUpdateStrategy]",
391         self];
392   }
393   updateStrategy     = aStrategy;
394   isKeepingSnapshots = (updateStrategy == EOUpdateWithNoLocking) ? NO : YES;
395   isUniquingObjects  = [database uniquesObjects];
396 }
397
398 - (EOUpdateStrategy)updateStrategy {
399   return self->updateStrategy;
400 }
401
402 - (BOOL)keepsSnapshots {
403   return self->isKeepingSnapshots;
404 }
405
406 /*
407  * Processing transactions internally
408  */
409
410 - (void)privateBeginTransaction {
411   EOTransactionScope *newScope = NULL;
412
413   newScope = _newTxScope([self zone]);
414   newScope->previous  = transactionNestingLevel ? transactionStackTop : NULL;
415   transactionStackTop = newScope;
416   transactionNestingLevel++;
417     
418   if (transactionNestingLevel == 1)
419     self->isUniquingObjects = [database uniquesObjects];
420 }
421
422 - (void)privateCommitTransaction {
423   EOTransactionScope *newScope = transactionStackTop;
424   
425   transactionStackTop = newScope->previous;
426   transactionNestingLevel--;
427     
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];
442   }
443   // If this was the first transaction then fold the changes 
444   // into the database; locked and updateted objects are forgotten
445   else {
446     int i, n;
447         
448     for (i = 0, n = [newScope->objectsDeleted count]; i < n; i++)
449       [database forgetObject:[newScope->objectsDeleted objectAtIndex:i]];
450     
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]];
456     }
457   }
458
459   // Kill transaction scope
460   _freeTxScope([self zone], newScope);
461 }
462
463 - (void)privateRollbackTransaction {
464   EOTransactionScope *newScope = transactionStackTop;
465   
466   transactionStackTop = newScope->previous;
467   transactionNestingLevel--;
468     
469   // Forget snapshots, updated, deleted and locked objects
470   // in current transaction
471     
472   // Kill transaction scope
473   _freeTxScope([self zone], newScope);
474 }
475
476 // Handle Objects
477
478 - (void)forgetObject:(id)_object {
479   EOTransactionScope *scope = NULL;
480
481   _checkTxInProgress(self, __PRETTY_FUNCTION__);
482
483   if (_object == nil) {
484     [NSException raise:NSInvalidArgumentException
485                  format:
486               @"EODatabaseContext:%x: Cannot forget null object, "
487               @"in [EODatabaseContext forgetObject]",
488               self];
489   }
490   if ([EOFault isFault:_object]) {
491     [NSException raise:NSInvalidArgumentException
492                  format:
493               @"EODatabaseContext:%x: Cannot forget forget a fault object, "
494               @"in [EODatabaseContext forgetObject]",
495               self];
496   }
497     
498   [transactionStackTop->objectsDeleted addObject:_object];
499
500   for (scope = transactionStackTop; scope; scope = scope->previous) {
501     [scope->objectsDictionary forgetObject:_object];
502   }
503 }
504
505 - (id)objectForPrimaryKey:(NSDictionary *)_key entity:(EOEntity *)_entity {
506   EOTransactionScope *scope  = NULL;
507   id                 _object = nil;
508     
509   if (!self->isUniquingObjects || (_key == nil) || (_entity == nil))
510     return nil;
511     
512   _key = [_entity primaryKeyForRow:_key];
513   if (_key == nil) return nil;
514     
515   for (scope = transactionStackTop; scope; scope = scope->previous) {
516     _object = [scope->objectsDictionary objectForPrimaryKey:_key 
517                                         entity:_entity];
518     if (_object)
519       return _object;
520   }
521     
522   return [self->database objectForPrimaryKey:_key entity:_entity];
523 }
524
525 - (void)recordObject:(id)_object
526   primaryKey:(NSDictionary *)_key 
527   entity:(EOEntity *)_entity
528   snapshot:(NSDictionary *)snapshot
529 {
530   _checkTxInProgress(self, __PRETTY_FUNCTION__);
531
532   if (_object == nil) {
533     [NSException raise:NSInvalidArgumentException
534                  format:
535               @"EODatabaseContext:%x: Cannot record null object, "
536               @"in [EODatabaseContext recordObject:primaryKey:entity:snapshot:]",
537               self];
538   }
539   if ((_entity == nil) && self->isUniquingObjects) {
540     [NSException raise:NSInvalidArgumentException
541                  format:
542               @"EODatabaseContext:%x: Cannot record object with null entity "
543               @"when uniquing objects, "
544               @"in [EODatabaseContext recordObject:primaryKey:entity:snapshot:]",
545               self];
546   }
547   
548   _key = [_entity primaryKeyForRow:_key];
549   
550   if ((_key == nil) && self->isUniquingObjects) {
551     [NSException raise:NSInvalidArgumentException
552                  format:
553               @"EODatabaseContext:%x: Cannot record object with null key "
554               @"when uniquing objects, "
555               @"in [EODatabaseContext recordObject:primaryKey:entity:snapshot:]",
556               self];
557   }
558   if ((snapshot == nil) && isKeepingSnapshots && ![EOFault isFault:_object]) {
559     [NSException raise:NSInvalidArgumentException
560                  format:
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",
565               self,
566               snapshot ? "yes" : "no",
567               isKeepingSnapshots ? "yes" : "no",
568               [EOFault isFault:_object] ? "yes" : "no"];
569   }
570     
571   if (self->isKeepingSnapshots || self->isUniquingObjects) {
572     EOObjectUniquer *cache = transactionStackTop->objectsDictionary;
573     
574     [cache recordObject:_object 
575            primaryKey:  self->isUniquingObjects  ? _key     : nil
576            entity:      self->isUniquingObjects  ? _entity : nil
577            snapshot:    self->isKeepingSnapshots ? snapshot : nil];
578   }
579 }
580
581 - (void)recordObject:(id)_object
582   primaryKey:(NSDictionary *)_key 
583   snapshot:(NSDictionary *)_snapshot
584 {
585   EOEntity *entity = nil;
586
587   entity = [_object respondsToSelector:@selector(entity)]
588     ? [_object entity]
589     : [[[database adaptor] model] entityForObject:_object];
590     
591   [self recordObject:_object primaryKey:_key entity:entity snapshot:_snapshot];
592 }
593
594 - (NSDictionary *)snapshotForObject:(id)_object {
595   EOTransactionScope *scope = NULL;
596   EOUniquerRecord    *rec   = NULL;
597     
598   if (!isKeepingSnapshots)
599     return nil;
600     
601   for (scope = transactionStackTop; scope; scope = scope->previous) {
602     rec = [scope->objectsDictionary recordForObject:_object];
603     if (rec)
604       return rec->snapshot;
605   }
606     
607   rec = [[self->database objectUniquer] recordForObject:_object];
608   if (rec) return rec->snapshot;
609     
610   return nil;
611 }
612
613 - (NSDictionary*)primaryKeyForObject:(id)_object {
614   EOTransactionScope *scope = NULL;
615   EOUniquerRecord    *rec   = NULL;
616     
617   if ([self->database uniquesObjects])
618     return nil;
619     
620   for (scope = transactionStackTop; scope; scope = scope->previous) {
621     rec = [scope->objectsDictionary recordForObject:_object];
622     if (rec) return rec->pkey;
623   }
624     
625   rec = [[self->database objectUniquer] recordForObject:_object];
626   if (rec) return rec->pkey;
627   return nil;
628 }
629
630 - (void)primaryKey:(NSDictionary **)_key
631   andSnapshot:(NSDictionary **)_snapshot
632   forObject:_object
633 {
634   EOTransactionScope *scope = NULL;
635   EOUniquerRecord    *rec   = NULL;
636
637   if (!self->isKeepingSnapshots && ![self->database uniquesObjects]) {
638     *_key = *_snapshot = nil;
639     return;
640   }
641     
642   for (scope = transactionStackTop; scope; scope = scope->previous) {
643     rec = [scope->objectsDictionary recordForObject:_object];
644     if (rec) {
645       if (_key)      *_key      = rec->pkey;
646       if (_snapshot) *_snapshot = rec->snapshot;
647       return;
648     }
649   }
650     
651   rec = [[self->database objectUniquer] recordForObject:_object];
652   if (rec) {
653     if (_key)      *_key      = rec->pkey;
654     if (_snapshot) *_snapshot = rec->snapshot;
655     return;
656   }
657     
658   if (_key)      *_key = nil;
659   if (_snapshot) *_snapshot = nil;
660 }
661
662 - (void)recordLockedObject:(id)_object {
663   _checkTxInProgress(self, __PRETTY_FUNCTION__);
664   
665   if (_object == nil) {
666     [NSException raise:NSInvalidArgumentException
667                  format:
668               @"EODatabaseContext:%x: Cannot record null object as locked, "
669               @"in [EODatabaseContext recordLockedObject:]",
670               self];
671   }
672   if ([EOFault isFault:_object]) {
673     [NSException raise:NSInvalidArgumentException
674                  format:
675               @"EODatabaseContext:%x: Cannot record a fault object as locked, "
676               @"in [EODatabaseContext recordLockedObject:]",
677               self];
678   }
679   [transactionStackTop->objectsLocked addObject:_object];
680 }
681
682 - (BOOL)isObjectLocked:(id)_object {
683   EOTransactionScope *scope;
684     
685   for (scope = transactionStackTop; scope; scope = scope->previous) {
686     if ([scope->objectsLocked indexOfObjectIdenticalTo:_object]!=NSNotFound)
687       return YES;
688   }
689   return NO;
690 }
691
692 - (void)recordUpdatedObject:(id)_object {
693   _checkTxInProgress(self, __PRETTY_FUNCTION__);
694
695   if (_object == nil) {
696     [NSException raise:NSInvalidArgumentException
697                  format:
698                @"EODatabaseContext:%x: Cannot record null object as updatetd, "
699                @"in [EODatabaseContext recordUpdatedObject:]",
700                self];
701   }
702   if ([EOFault isFault:_object]) {
703     [NSException raise:NSInvalidArgumentException
704                  format:
705                @"EODatabaseContext:%x: Cannot record fault object as updated, "
706                @"in [EODatabaseContext recordUpdatedObject:]",
707                self];
708   }
709     
710   [transactionStackTop->objectsUpdated addObject:_object];
711 }
712
713 - (BOOL)isObjectUpdated:(id)_object {
714   EOTransactionScope *scope;
715     
716   for (scope = transactionStackTop; scope; scope = scope->previous) {
717     if ([scope->objectsUpdated indexOfObjectIdenticalTo:_object] != NSNotFound)
718       return YES;
719     if ([scope->objectsDeleted indexOfObjectIdenticalTo:_object] != NSNotFound)
720       return YES;
721   }
722   return NO;
723 }
724
725 /* description */
726
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]];
733 }
734
735 @end /* EODatabaseContext */
736
737 @implementation EODatabaseContext(Statistics)
738
739 - (unsigned int)transactionBeginCount {
740   return self->txBeginCount;
741 }
742 - (unsigned int)transactionCommitCount {
743   return self->txCommitCount;
744 }
745 - (unsigned int)transactionRollbackCount {
746   return self->txRollbackCount;
747 }
748
749 @end /* EODatabaseContext(Statistics) */
750
751 @implementation EODatabaseContext(NewInEOF2)
752
753 // THREAD
754 static Class EODatabaseContextClass = Nil;
755
756 + (void)setContextClassToRegister:(Class)_cclass {
757   EODatabaseContextClass = _cclass;
758 }
759 + (Class)contextClassToRegister {
760   return EODatabaseContextClass ? EODatabaseContextClass : self;
761 }
762
763 - (EODatabaseChannel *)availableChannel {
764   int i;
765     
766   for (i = [self->channels count] - 1; i >= 0; i--) {
767     EODatabaseChannel *channel;
768
769     channel = [[self->channels objectAtIndex:i] nonretainedObjectValue];
770     if (![channel isFetchInProgress])
771       return channel;
772   }
773
774   [[NSNotificationCenter defaultCenter]
775                          postNotificationName:@"EODatabaseChannelNeeded"
776                          object:self];
777
778   /* recheck for channel */
779   
780   for (i = [self->channels count] - 1; i >= 0; i--) {
781     EODatabaseChannel *channel;
782
783     channel = [[self->channels objectAtIndex:i] nonretainedObjectValue];
784     if (![channel isFetchInProgress])
785       return channel;
786   }
787
788   return nil;
789 }
790
791 - (NSArray *)registeredChannels {
792   NSMutableArray *array;
793   int i, n;
794     
795   array = [NSMutableArray array];
796   for (i=0, n=[channels count]; i < n; i++) {
797     EODatabaseChannel *channel = 
798       [[channels objectAtIndex:i] nonretainedObjectValue];
799     
800     [array addObject:channel];
801   }
802     
803   return array;
804 }
805 - (void)registerChannel:(EODatabaseChannel *)_channel {
806   [self->channels addObject:[NSValue valueWithNonretainedObject:_channel]];
807 }
808 - (void)unregisterChannel:(EODatabaseChannel *)_channel {
809   int i;
810     
811   for (i = [self->channels count] - 1; i >= 0; i--) {
812     EODatabaseChannel *channel;
813
814     channel = [[self->channels objectAtIndex:i] nonretainedObjectValue];
815     
816     if (channel == _channel) {
817       [channels removeObjectAtIndex:i];
818       break;
819     }
820   }
821 }
822
823 /* cooperating object store */
824
825 - (void)commitChanges {
826   [self commitTransaction];
827 }
828 - (void)rollbackChanges {
829   [self rollbackTransaction];
830 }
831
832 - (void)performChanges {
833   [self notImplemented:_cmd];
834 }
835
836 /* store specific properties */
837
838 - (NSDictionary *)valuesForKeys:(NSArray *)_keys object:(id)_object {
839   return [_object valuesForKeys:_keys];
840 }
841
842 /* capability */
843
844 - (BOOL)handlesFetchSpecification:(EOFetchSpecification *)_fspec {
845   EOEntity *entity;
846
847   entity = [[self database] entityNamed:[_fspec entityName]];
848   return entity ? YES : NO;
849 }
850
851 /* graph */
852
853 - (BOOL)ownsObject:(id)_object {
854   EOEntity *entity;
855   
856   entity = [[self database] entityNamed:[_object entityName]];
857   return entity ? YES : NO;
858 }
859
860 - (BOOL)ownsGlobalID:(EOGlobalID *)_oid {
861   EOEntity *entity;
862   
863   if (![_oid respondsToSelector:@selector(entityName)])
864     return NO;
865
866   entity = [[self database] entityNamed:[(id)_oid entityName]];
867   return entity ? YES : NO;
868 }
869
870 @end /* EODatabaseContext(NewInEOF2) */