]> err.no Git - sope/blob - sope-gdl1/GDLAccess/EODatabaseChannel.m
renamed PostgreSQL72 to PostgreSQL, install in Library/GDLAdaptors-1.1
[sope] / sope-gdl1 / GDLAccess / EODatabaseChannel.m
1 /* 
2    EODatabaseChannel.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    This file is part of the GNUstep Database Library.
10
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.
15
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.
20
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.
25 */
26
27 #import "common.h"
28 #import "EODatabaseChannel.h"
29 #import "EOAdaptor.h"
30 #import "EOAdaptorChannel.h"
31 #import "EOAdaptorContext.h"
32 #import "EOAttribute.h"
33 #import "EODatabase.h"
34 #import "EODatabaseContext.h"
35 #import "EOEntity.h"
36 #import "EODatabaseFault.h"
37 #import "EOGenericRecord.h"
38 #import "EOModel.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>
45
46 @class EOGenericRecord;
47
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";
74
75 /*
76  * Private methods declaration
77  */
78
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, ...;
85 @end
86
87 /*
88  * EODatabaseChannel implementation
89  */
90
91 @implementation EODatabaseChannel
92
93 /*
94  * Initializing a new instance
95  */
96
97 - (id)initWithDatabaseContext:(EODatabaseContext *)_dbContext {
98   if (_dbContext == nil) {
99     AUTORELEASE(self);
100     return nil;
101   }
102   
103   self->notificationCenter = RETAIN([NSNotificationCenter defaultCenter]);
104   
105   self->databaseContext = RETAIN(_dbContext);
106   [self setDelegate:[self->databaseContext delegate]];
107   [self->databaseContext channelDidInit:self];
108   
109   return self;
110 }
111
112 - (void)dealloc {
113   [self->databaseContext channelWillDealloc:self];
114   RELEASE(self->currentEditingContext);
115   RELEASE(self->databaseContext);
116   RELEASE(self->adaptorChannel);
117   RELEASE(self->notificationCenter);
118   [super dealloc];
119 }
120
121 // notifications
122
123 - (void)postNotification:(NSString *)_name {
124   [self->notificationCenter postNotificationName:_name object:self];
125 }
126 - (void)postNotification:(NSString *)_name object:(id)_obj {
127   [self->notificationCenter postNotificationName:_name
128                             object:self
129                             userInfo:[NSDictionary dictionaryWithObject:_obj
130                                                    forKey:@"object"]];
131 }
132
133 // accessors
134
135 - (EOAdaptorChannel *)adaptorChannel {
136   if (self->adaptorChannel == nil) {
137     static int reuseAdaptorCh = -1;
138     if (reuseAdaptorCh == -1) {
139       reuseAdaptorCh = [[[NSUserDefaults standardUserDefaults]
140                                          objectForKey:@"EOReuseAdaptorChannel"]
141                                          boolValue] ? 1 : 0;
142     }
143     
144     if (reuseAdaptorCh) {
145       NSEnumerator     *channels;
146       EOAdaptorChannel *channel;
147       
148       channels =
149         [[[[self databaseContext] adaptorContext] channels] objectEnumerator];
150       
151       while ((channel = [channels nextObject])) {
152         if ([channel isFetchInProgress])
153           continue;
154
155 #if DEBUG
156         NSLog(@"reuse adaptor channel: %@", channel);
157 #endif
158         self->adaptorChannel = channel;
159         break;
160       }
161     }
162     
163     if (self->adaptorChannel == nil) {
164       self->adaptorChannel =
165         [[[self databaseContext] adaptorContext] createAdaptorChannel];
166     }
167     
168     RETAIN(self->adaptorChannel);
169   }
170   return self->adaptorChannel;
171 }
172
173 - (EODatabaseContext *)databaseContext {
174   return self->databaseContext;
175 }
176
177 // delegate
178
179 - (void)setDelegate:(id)_delegate {
180   self->delegate = _delegate;
181 }
182 - (id)delegate {
183   return self->delegate;
184 }
185
186 // Opening and closing a channel
187
188 - (BOOL)isOpen {
189   return [[self adaptorChannel] isOpen];
190 }
191
192 - (BOOL)openChannel {
193   BOOL result;
194
195   [self postNotification:EODatabaseChannelWillOpenNotificationName];
196   
197   if ((result = [[self adaptorChannel] openChannel])) {
198     self->successfulOpenCount++;
199     [self postNotification:EODatabaseChannelDidOpenNotificationName];
200   }
201   else {
202     self->failedOpenCount++;
203     [self postNotification:EODatabaseChannelCouldNotOpenNotificationName];
204   }
205
206   return result;
207 }
208
209 - (void)closeChannel {
210   [self postNotification:EODatabaseChannelWillCloseNotificationName];
211   [[self adaptorChannel] closeChannel];
212   self->closeCount++;
213   [self postNotification:EODatabaseChannelDidCloseNotificationName];
214 }
215
216 // Modifying objects
217
218 - (BOOL)_isNoRaiseOnModificationException:(NSException *)_exception {
219   /* for compatibility with non-X methods, translate some errors to a bool */
220   NSString *n;
221   
222   n = [_exception name];
223   if ([n isEqualToString:@"EOEvaluationError"])
224     return YES;
225   if ([n isEqualToString:@"EODelegateRejects"])
226     return YES;
227
228   return NO;
229 }
230
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;
240   int i;
241   
242   [self postNotification:EODatabaseChannelWillInsertObjectName object:anObj];
243
244   if (![anObj prepareForInsertInChannel:self context:self->databaseContext])
245     return NO;
246   
247   // Check the delegate
248   if ([self->delegate respondsToSelector:
249                         @selector(databaseChannel:willInsertObject:)])
250     anObj = [delegate databaseChannel:self willInsertObject:anObj];
251
252   // Check nil (delegate disallowes or given object was nil)
253   if (anObj == nil)
254     return NO;
255   
256   // Check if fault
257   if ([EOFault isFault:anObj]) {
258     [NSException raise:NSInvalidArgumentException
259                  format:@"Attempt to insert a fault in a database channel"];
260   }
261
262   // Check if we can insert
263   if ([databaseContext updateStrategy] == EONoUpdate) {
264     [self privateReportError:_cmd : 
265           @"cannot insert if context has 'NoUpdate' update strategy."];
266     return NO;
267   }
268   
269   /* validate object for insert */
270   
271   if ((exception = [anObj validateForInsert])) {
272     /* validation failed */
273     [exception raise];
274   }
275   
276   // Check if in a transaction
277   if (![databaseContext transactionNestingLevel]) {
278     [self privateReportError:_cmd : 
279           @"cannot insert if contex has no transaction opened."];
280     return NO;
281   }
282     
283   // Get entity
284   entity = [anObj respondsToSelector:@selector(entity)]
285     ? [anObj entity]
286     : [[[[adaptorChannel adaptorContext] adaptor] model] entityForObject:anObj];
287     
288   // Check entity
289   if (entity == nil) {
290     [self privateReportError:_cmd : 
291             @"cannot determine entity for object %p class %@.",
292             anObj, NSStringFromClass([anObj class])];
293     return NO;
294   }
295   if ([entity isReadOnly]) {
296     [self privateReportError:_cmd : 
297             @"cannot insert object %p for readonly entity %@.",
298             anObj, [entity name]];
299     return NO;
300   }
301
302   // Get array of attributes to insert
303   attributes = [entity attributesUsedForInsert];
304
305   // Get simple values and convert them to adaptor values
306   values = [anObj valuesForKeys:[entity attributesNamesUsedForInsert]];
307   values = [entity convertValuesToModel:values];
308     
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];
312
313     NSAssert(attribute, @"invalid attribute object ..");
314     
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]];
319       return NO;
320     }
321   }
322     
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]];
329     return NO;
330   }
331   pkey = [entity primaryKeyForRow:values];
332   if (pkey == nil) {
333     [self privateReportError:_cmd : 
334             @"cannot determine primary key for %p from values %@ entity %@.",
335             anObj, [values description], [entity name]];
336     return NO;
337   }
338     
339   // Insert adaptor row
340   exception = [adaptorChannel insertRowX:values forEntity:entity];
341   if (exception) {
342     if (![self _isNoRaiseOnModificationException:exception]) [exception raise];
343     return NO;
344   }
345   
346   // Record object in database context
347   [databaseContext recordObject:anObj 
348                    primaryKey:pkey entity:entity snapshot:values];
349
350   self->insertCount++;
351   [anObj wasInsertedInChannel:self context:self->databaseContext];
352   
353   // Notify delegate
354   if ([delegate respondsToSelector:@selector(databaseChannel:didInsertObject:)])
355     [delegate databaseChannel:self didInsertObject:anObj];
356
357   [self postNotification:EODatabaseChannelDidInsertObjectName object:anObj];
358   return YES;
359 }
360
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;
373
374   [self postNotification:EODatabaseChannelWillUpdateObjectName object:anObj];
375
376   if (![anObj prepareForUpdateInChannel:self context:self->databaseContext])
377     return NO;
378   
379   // Check the delegate
380   if ([delegate respondsToSelector:@selector(databaseChannel:willUpdateObject:)])
381     anObj = [delegate databaseChannel:self willUpdateObject:anObj];
382
383   // Check nil (delegate disallowes or given object was nil)
384   if (anObj == nil)
385     return NO;
386
387   // Check if fault
388   if ([EOFault isFault:anObj]) {
389     [NSException raise:NSInvalidArgumentException
390                  format:@"Attempt to update a fault in a database channel"];
391   }
392
393   // Check if we can update
394   if ([databaseContext updateStrategy] == EONoUpdate) {
395     [self privateReportError:_cmd : 
396           @"cannot update if context has 'NoUpdate' update strategy."];
397     return NO;
398   }
399
400   /* validate object for update */
401   
402   if ((exception = [anObj validateForUpdate])) {
403     /* validation failed */
404     [exception raise];
405   }
406   
407   // Check if in a transaction
408   if (![databaseContext transactionNestingLevel]) {
409     [self privateReportError:_cmd : 
410             @"cannot update if contex has no transaction opened."];
411     return NO;
412   }
413     
414   // Get entity
415   entity = [anObj respondsToSelector:@selector(entity)]
416     ? [anObj entity]
417     : [[[[adaptorChannel adaptorContext] adaptor] model] entityForObject:anObj];
418     
419   // Check entity
420   {
421     if (entity == nil) {
422       [self privateReportError:_cmd : 
423             @"cannot determine entity for object %p class %@.",
424             anObj, NSStringFromClass([anObj class])];
425       return NO;
426     }
427     if ([entity isReadOnly]) {
428       [self privateReportError:_cmd : 
429             @"cannot update object %p for readonly entity %@.",
430             anObj, [entity name]];
431       return NO;
432     }
433   }
434   
435   // Get and check old snapshot and primary key
436   {
437     [databaseContext primaryKey:&old_pkey
438                      andSnapshot:&old_snapshot
439                      forObject:anObj];
440   
441     if (old_snapshot == nil) {
442       [self privateReportError:_cmd : 
443             @"cannot update object %p because there is no snapshot for it."];
444       return NO;
445     }
446     if (old_pkey == nil)
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]];
452       return NO;
453     }
454   }
455     
456   // Get simple values and convert them to adaptor values
457   values = [anObj valuesForKeys:[entity attributesNamesUsedForInsert]];
458   values = [entity convertValuesToModel:values];
459     
460   // Get and check new primary key and snapshot
461   {
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]];
467       return NO;
468     }
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]];
474       return NO;
475     }
476   }
477     
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];
484       break;
485     case EOUpdateWithNoLocking:
486       needsOptimisticLock = NO;
487       break;
488     default:
489       return NO;
490   }
491         
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) {
495     int i;
496     BOOL         canUseQualifier = YES;
497     NSArray      *lockAttrs = [entity attributesUsedForLocking];
498     EOAdaptor    *adaptor   = [[adaptorChannel adaptorContext] adaptor];
499     NSDictionary *row;
500         
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;
506         break;
507       }
508     }
509         
510     if (canUseQualifier)
511       // If YES just build the qualifier
512       qualifier = [EOSQLQualifier qualifierForRow:old_snapshot 
513                                entity:entity];
514     else {
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 
518                                entity:entity];
519 #ifdef DEBUG
520       NSAssert2([lockAttrs count] > 0,
521                 @"missing locking attributes: lock=%@ object=%@",
522                 lockAttrs, anObj);
523 #endif
524       if (![adaptorChannel selectAttributes:lockAttrs
525                            describedByQualifier:qualifier
526                            fetchOrder:nil 
527                            lock:YES]) {
528         [self privateReportError:_cmd : 
529                 @"could not lock=%@ with qualifier=%@ entity=%@.",
530                 anObj, [qualifier description], [entity name]];
531         return NO;
532       }
533       row = [adaptorChannel fetchAttributes:lockAttrs withZone:NULL];
534       [adaptorChannel cancelFetch];
535       if (row == nil) {
536         [self privateReportError:_cmd : 
537               @"could not get row to lock %p with qualifier %@.",
538               anObj, [qualifier description]];
539         return NO;
540       }
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]];
546         return NO;
547       }
548     }
549   }
550   else {
551     qualifier = [EOSQLQualifier qualifierForPrimaryKey:old_pkey 
552                              entity:entity];
553   }
554     
555   // Compute values as delta from values and old_snapshot
556   {
557     NSMutableDictionary *delta;
558     NSString            *attributeName;
559     NSArray             *allKeys;
560     int                 i, count;
561         
562     allKeys = [values allKeys];
563     delta   = [NSMutableDictionary dictionary];
564     for (i = 0, count = [allKeys count]; i < count; i++) {
565       id new_v, old_v;
566
567       attributeName = [allKeys objectAtIndex:i];
568       new_v         = [values objectForKey:attributeName];
569       old_v         = [old_snapshot objectForKey:attributeName];
570       
571       if ((old_v == nil) || ![new_v isEqual:old_v])
572         [delta setObject:new_v forKey:attributeName];
573     }
574     values = delta;
575   }
576
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
579   // was deleted
580   if ([values count] == 0) {
581     if (![self refetchObject:anObj])
582       return NO;
583   }
584   // Update in adaptor
585   else {
586     NSException *ex;
587     
588     ex = [adaptorChannel updateRowX:values describedByQualifier:qualifier];
589     if (ex != nil) {
590       if (![self _isNoRaiseOnModificationException:ex]) [ex raise];
591       return NO;
592     }
593   }
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];
599   }
600   
601   [databaseContext recordObject:anObj 
602                    primaryKey:new_pkey
603                    entity:entity
604                    snapshot:new_snapshot];
605   [databaseContext recordUpdatedObject:anObj];
606
607   self->updateCount++;
608   [anObj wasUpdatedInChannel:self context:self->databaseContext];
609   
610   // Notify delegate
611   if ([delegate respondsToSelector:@selector(databaseChannel:didUpdateObject:)])
612     [delegate databaseChannel:self didUpdateObject:anObj];
613
614   [self postNotification:EODatabaseChannelDidUpdateObjectName object:anObj];
615   return YES;
616 }
617
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;
626
627   [self postNotification:EODatabaseChannelWillDeleteObjectName object:anObj];
628
629   if (![anObj prepareForDeleteInChannel:self context:self->databaseContext])
630     return NO;
631   
632   // Check the delegate
633   if ([delegate respondsToSelector:@selector(databaseChannel:willDeleteObject:)])
634     anObj = [delegate databaseChannel:self willDeleteObject:anObj];
635
636   // Check nil (delegate disallowes or given object was nil)
637   if (anObj == nil)
638     return NO;
639
640   // Check if fault
641   if ([EOFault isFault:anObj]) {
642     [NSException raise:NSInvalidArgumentException
643                  format:@"Attempt to delete a fault in a database channel"];
644   }
645
646   // Check if we can delete
647   if ([databaseContext updateStrategy] == EONoUpdate) {
648     [self privateReportError:_cmd : 
649             @"cannot delete if context has 'NoUpdate' update strategy."];
650     return NO;
651   }
652
653   /* validate object for delete */
654   
655   if ((exception = [anObj validateForDelete])) {
656     /* validation failed */
657     [exception raise];
658   }
659   
660   // Check if in a transaction
661   if (![databaseContext transactionNestingLevel]) {
662     [self privateReportError:_cmd : 
663             @"cannot update if contex has no transaction opened."];
664     return NO;
665   }
666   
667   // Get entity
668   entity = [anObj respondsToSelector:@selector(entity)]
669     ? [anObj entity]
670     : [[[[adaptorChannel adaptorContext] adaptor] model] entityForObject:anObj];
671   
672   // Check entity
673   if (entity == nil) {
674     [self privateReportError:_cmd : 
675             @"cannot determine entity for object %p class %s.",
676             anObj, NSStringFromClass([anObj class])];
677     return NO;
678   }
679   if ([entity isReadOnly]) {
680     [self privateReportError:_cmd : 
681             @"cannot delete object %p for readonly entity %@.",
682             anObj, [entity name]];
683     return NO;
684   }
685     
686   // Get snapshot and old primary key
687   [databaseContext primaryKey:&pkey andSnapshot:&snapshot forObject:anObj];
688   if (pkey == nil) {
689     if (snapshot == nil)
690       [self privateReportError:_cmd : 
691             @"cannot delete object %p because there is no snapshot for it."];
692     pkey = [entity primaryKeyForRow:snapshot];
693   }
694   if (pkey == nil) {
695     [self privateReportError:_cmd : 
696             @"cannot determine primary key for %p from values %@ entity %@.",
697             anObj, [snapshot description], [entity name]];
698     return NO;
699   }
700     
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]];
707     return NO;
708   }
709     
710   // Delete adaptor row
711   exception = [adaptorChannel deleteRowsDescribedByQualifierX:qualifier];
712   if (exception != nil) {
713     if (![self _isNoRaiseOnModificationException:exception]) [exception raise];
714     return NO;
715   }
716   
717   AUTORELEASE(RETAIN(anObj));
718   
719   // Forget object in database context
720   [databaseContext forgetObject:anObj];
721
722   self->deleteCount++;
723   [anObj wasDeletedInChannel:self context:self->databaseContext];
724   
725   // Notify delegate
726   if ([delegate respondsToSelector:
727                   @selector(databaseChannel:didDeleteObject:)])
728     [delegate databaseChannel:self didDeleteObject:anObj];
729
730   [self postNotification:EODatabaseChannelDidDeleteObjectName object:anObj];
731   return YES;
732 }
733
734 - (BOOL)lockObject:(id)anObj {
735   EOEntity     *entity    = nil;
736   NSDictionary *pkey      = nil;
737   NSDictionary *snapshot  = nil;
738   EOSQLQualifier  *qualifier = nil;
739
740   [self postNotification:EODatabaseChannelWillLockObjectName object:anObj];
741
742   if (![anObj prepareForLockInChannel:self context:self->databaseContext])
743     return NO;
744     
745   // Check the delegate
746   if ([delegate respondsToSelector:@selector(databaseChannel:willLockObject:)])
747     anObj = [delegate databaseChannel:self willLockObject:anObj];
748
749   // Check nil (delegate disallowes or given object was nil)
750   if (anObj == nil)
751     return NO;
752
753   // Check if fault
754   if ([EOFault isFault:anObj]) {
755     [NSException raise:NSInvalidArgumentException
756                  format:@"Attempt to lock a fault in a database channel"];
757   }
758
759   // Check if we can lock
760   if ([databaseContext updateStrategy] == EONoUpdate) {
761     [self privateReportError:_cmd : 
762             @"cannot lock if context has 'NoUpdate' update strategy."];
763     return NO;
764   }
765     
766   // Check if in a transaction
767   if (![databaseContext transactionNestingLevel]) {
768     [self privateReportError:_cmd : 
769             @"cannot lock if contex has no transaction opened."];
770     return NO;
771   }
772     
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."];
777     return NO;
778   }
779     
780   // Get entity
781   entity =  [anObj respondsToSelector:@selector(entity)]
782     ? [anObj entity]
783     : [[[[adaptorChannel adaptorContext] adaptor] model] entityForObject:anObj];
784     
785   // Check entity
786   if (entity == nil) {
787     [self privateReportError:_cmd : 
788             @"cannot determine entity for object %p class %s.",
789             anObj, NSStringFromClass([anObj class])];
790     return NO;
791   }
792   if ([entity isReadOnly]) {
793     [self privateReportError:_cmd : 
794             @"cannot lock object %p for readonly entity %@.",
795             anObj, [entity name]];
796     return NO;
797   }
798     
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."];
804     return NO;
805   }
806   
807   if (pkey == nil) 
808     pkey = [entity primaryKeyForRow:snapshot];
809
810   if (pkey == nil) {
811     [self privateReportError:_cmd : 
812             @"cannot determine primary key for %p from values %@ entity %@.",
813             anObj, [snapshot description], [entity name]];
814     return NO;
815   }
816     
817   {
818     NSArray      *lockAttrs = [entity attributesUsedForLocking];
819     NSDictionary *row       = nil;
820         
821     qualifier = [EOSQLQualifier qualifierForPrimaryKey:pkey entity:entity];
822         
823 #ifdef DEBUG
824       NSAssert2([lockAttrs count] > 0,
825                 @"missing locking attributes: lock=%@ object=%@",
826                 lockAttrs, anObj);
827 #endif
828     if (![adaptorChannel selectAttributes:lockAttrs
829                          describedByQualifier:qualifier
830                          fetchOrder:nil 
831                          lock:YES]) {
832       [self privateReportError:_cmd : 
833               @"could not lock %p with qualifier %@.",
834               anObj, [qualifier description]];
835       return NO;
836     }
837     row = [adaptorChannel fetchAttributes:lockAttrs withZone:NULL];
838     [adaptorChannel cancelFetch];
839     if (row == nil) {
840       [self privateReportError:_cmd : 
841               @"could not lock %p with qualifier %@.",
842               anObj, [qualifier description]];
843       return NO;
844     }
845     if (![row isEqual:snapshot]) {
846       [self privateReportError:_cmd : 
847               @"could not lock %p. Snapshots: self %@ database %@.",
848               anObj, [snapshot description], [row description]];
849       return NO;
850     }
851   }
852     
853   // Register lock object in database context
854   [databaseContext recordLockedObject:anObj];
855
856   self->lockCount++;
857   [anObj wasLockedInChannel:self context:self->databaseContext];
858   
859   // Notify delegate
860   if ([delegate respondsToSelector:@selector(databaseChannel:didLockObject:)])
861     [delegate databaseChannel:self didLockObject:anObj];
862   [self postNotification:EODatabaseChannelDidLockObjectName object:anObj];
863   return YES;
864 }
865
866 - (BOOL)refetchObject:(id)anObj {
867   EOEntity     *entity    = nil;
868   NSDictionary *pkey      = nil;
869   NSDictionary *snapshot  = nil;
870   EOSQLQualifier  *qualifier = nil;
871     
872   // Check the delegate
873   if ([delegate respondsToSelector:
874                   @selector(databaseChannel:willRefetchObject:)])
875     anObj = [delegate databaseChannel:self willRefetchObject:anObj];
876
877   // Check nil (delegate disallowes or given object was nil)
878   if (anObj == nil)
879     return NO;
880
881   // Check if fault
882   if ([EOFault isFault:anObj]) {
883     [NSException raise:NSInvalidArgumentException
884                  format:@"Attempt to refetch a fault in a database channel"];
885   }
886
887   // Check if in a transaction
888   if (![databaseContext transactionNestingLevel]) {
889     [self privateReportError:_cmd : 
890           @"cannot refetch if context has no transaction opened."];
891     return NO;
892   }
893     
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."];
898     return NO;
899   }
900     
901   // Get entity
902   entity = [anObj respondsToSelector:@selector(entity)]
903     ? [anObj entity]
904     : [[[[adaptorChannel adaptorContext] adaptor] model] entityForObject:anObj];
905     
906   // Check entity
907   if (entity == nil) {
908     [self privateReportError:_cmd : 
909           @"cannot determine entity for object %p class %s.",
910           anObj, NSStringFromClass([anObj class])];
911     return NO;
912   }
913     
914   // Get snapshot and old primary key
915   [databaseContext primaryKey:&pkey andSnapshot:&snapshot forObject:anObj];
916   if (pkey == nil) {
917     if (snapshot == nil)
918       [self privateReportError:_cmd : 
919               @"cannot refetch object %p because there is no snapshot for it."];
920     pkey = [entity primaryKeyForRow:snapshot];
921   }
922   if (pkey == nil) {
923     [self privateReportError:_cmd : 
924             @"cannot determine primary key for %p from values %@ entity %@.",
925             anObj, [snapshot description], [entity name]];
926     return NO;
927   }
928     
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]];
935     return NO;
936   }
937     
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];
944     return NO;
945   }
946     
947 #ifdef DEBUG
948   NSAssert3([currentAttributes count] > 0,
949             @"missing attributes for select: lock=%@ object=%@ entity=%@",
950             currentAttributes, anObj, entity);
951 #endif
952   if (![adaptorChannel selectAttributes:currentAttributes
953                        describedByQualifier:qualifier 
954                        fetchOrder:nil
955                        lock:([databaseContext updateStrategy] ==
956                              EOUpdateWithPessimisticLocking)]) {
957     [self privateClearCurrentEntityInfo];
958     return NO;
959   }
960
961   // Get object from adaptor, re-build its faults and record new snapshot
962   anObj = [self privateFetchWithZone:NULL];
963   [self cancelFetch];
964   if (anObj == nil) {
965     [self privateReportError:_cmd : 
966           @"could not refetch %p with qualifier %@.",
967           anObj, [qualifier description]];
968     return NO;
969   }
970
971   // Notify delegate
972   if ([delegate respondsToSelector:@selector(databaseChannel:didRefetchObject:)])
973     [delegate databaseChannel:self didRefetchObject:anObj];
974   return YES;
975 }
976
977 - (id)_createObjectForRow:(NSDictionary*)aRow entity:(EOEntity*)anEntity 
978   isPrimaryKey:(BOOL)yn zone:(NSZone*)zone {
979   Class class = Nil;
980   id    anObj = nil;
981     
982   if (anEntity == nil)
983     return nil;
984     
985   class = [self privateClassForEntity:anEntity];
986
987   // Create new instance
988   if ([class respondsToSelector:@selector(classForEntity:values:)])
989     class = [class classForEntity:anEntity values:aRow];
990   
991   anObj = [class allocWithZone:zone];
992     
993   return anObj;
994 }
995
996 - (id)allocateObjectForRow:(NSDictionary *)row entity:(EOEntity *)anEntity
997   zone:(NSZone *)zone {
998   
999   Class class = Nil;
1000   id    anObj = nil;
1001     
1002   if (anEntity == nil)
1003     return nil;
1004     
1005   class = [self privateClassForEntity:anEntity];
1006     
1007   // Create new instance
1008   if ([class respondsToSelector:@selector(classForEntity:values:)])
1009     class = [class classForEntity:anEntity values:row];
1010   
1011   anObj = [class allocWithZone:zone];
1012     
1013   return anObj;
1014 }
1015
1016 - (id)initializedObjectForRow:(NSDictionary *)row
1017   entity:(EOEntity *)anEntity
1018   zone:(NSZone *)zone
1019 {
1020   id anObj;
1021   
1022   anObj = [self allocateObjectForRow:row entity:anEntity zone:zone];
1023   
1024   anObj = [anObj respondsToSelector:@selector(initWithPrimaryKey:entity:)]
1025     ? [anObj initWithPrimaryKey:row entity:anEntity]
1026     : [anObj init];
1027     
1028   return AUTORELEASE(anObj);
1029 }
1030
1031 /*
1032  * Fetching objects
1033  */
1034
1035 - (id)_fetchObject:(id)anObj qualifier:(EOSQLQualifier *)qualifier {
1036   id obj;
1037     
1038   [self selectObjectsDescribedByQualifier:qualifier fetchOrder:nil];
1039   obj = [self fetchWithZone:NULL];
1040   [self cancelFetch];
1041   return obj;
1042 }
1043
1044 - (BOOL)selectObjectsDescribedByQualifier:(EOSQLQualifier *)qualifier
1045   fetchOrder:(NSArray *)fetchOrder
1046 {
1047   if ([delegate respondsToSelector:
1048        @selector(databaseChannel:willSelectObjectsDescribedByQualifier:fetchOrder:)])
1049     if (![delegate databaseChannel:self 
1050                    willSelectObjectsDescribedByQualifier:qualifier
1051                    fetchOrder:fetchOrder])
1052       return NO;
1053  
1054   [self setCurrentEntity:[qualifier entity]];
1055   [self privateUpdateCurrentEntityInfo];
1056   if (self->currentAttributes == nil) {
1057     [self privateReportError:_cmd : 
1058           @"internal inconsitency while selecting."];
1059   }
1060 #ifdef DEBUG
1061   NSAssert3([self->currentAttributes count] > 0,
1062             @"missing select attributes: attrs=%@, qualifier=%@, entity=%@",
1063             self->currentAttributes, qualifier, self->currentEntity);
1064 #endif
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]];
1074     return NO;
1075   }
1076
1077   if ([delegate respondsToSelector:
1078        @selector(databaseChannel:didSelectObjectsDescribedByQualifier:fetchOrder:)])
1079     [delegate databaseChannel:self 
1080               didSelectObjectsDescribedByQualifier:qualifier
1081               fetchOrder:fetchOrder];
1082   return YES;
1083 }
1084
1085 - (id)fetchWithZone:(NSZone *)zone {
1086   id object = nil;
1087     
1088   if ([delegate respondsToSelector:
1089                   @selector(databaseChannel:willFetchObjectOfClass:withZone:)]) {
1090     Class class;
1091
1092     class = currentClass
1093       ? currentClass
1094       : [self privateClassForEntity:currentEntity];
1095     
1096     [delegate databaseChannel:self 
1097               willFetchObjectOfClass:class
1098               withZone:zone];
1099   }
1100   object = [self privateFetchWithZone:zone];
1101   if (object == nil)
1102     return nil;
1103
1104   if ([delegate respondsToSelector:@selector(databaseChannel:didFetchObject:)])
1105     [delegate databaseChannel:self didFetchObject:object];
1106   
1107   return object;
1108 }
1109
1110 - (BOOL)isFetchInProgress {
1111   return [[self adaptorChannel] isFetchInProgress];
1112 }
1113
1114 - (void)cancelFetch {
1115   if ([[self adaptorChannel] isFetchInProgress]) {
1116     [self privateClearCurrentEntityInfo];
1117     [[self adaptorChannel] cancelFetch];
1118   }
1119 }
1120
1121 - (void)setCurrentEntity:(EOEntity *)_entity {
1122   // Clear entity info
1123   [self privateClearCurrentEntityInfo];
1124   // Set new entity
1125   NSAssert(self->currentEntity == nil, @"entity not cleared correctly ..");
1126   self->currentEntity = RETAIN(_entity);
1127 }
1128
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;
1135 }
1136
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"];
1142   }
1143   
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;
1150 }
1151
1152 /*
1153  * Private methods
1154  */
1155
1156 - (Class)privateClassForEntity:(EOEntity *)anEntity {
1157   Class class;
1158     
1159   if (anEntity == currentEntity && currentClass)
1160     return currentClass;
1161     
1162     // Get class for new object
1163   class = NSClassFromString([anEntity className]);
1164
1165   if (!class && [delegate respondsToSelector:
1166                           @selector(databaseChannel:failedToLookupClassNamed:)])
1167     class = [delegate databaseChannel:self 
1168                       failedToLookupClassNamed:[[anEntity className] cString]];
1169   if (class == Nil)
1170     class = [EOGenericRecord class];
1171
1172   if (anEntity == currentEntity)
1173     currentClass = class;
1174     
1175   return class;
1176 }
1177
1178 - (id)privateFetchWithZone:(NSZone *)_zone {
1179   NSMutableDictionary *values = nil;
1180   id           object    = nil;
1181   NSDictionary *pkey     = nil;
1182   NSDictionary *snapshot = nil;
1183   NSDictionary *row      = nil;
1184   NSDictionary *dict     = nil;;
1185     
1186   // Be sure we have entity info (raises if no entity is set)
1187   if (!self->currentReady)
1188     [self privateUpdateCurrentEntityInfo];
1189     
1190   // fetch row from adaptor
1191   row = [[self adaptorChannel] fetchAttributes:self->currentAttributes
1192                               withZone:_zone];
1193   if (row == nil)
1194     // Results set finished or no more result sets
1195     return nil;
1196 #if 0
1197   row = [row copyWithZone:_zone];
1198   AUTORELEASE(row);
1199 #endif
1200   
1201   // determine primary key and snapshot
1202   snapshot = [self->currentEntity snapshotForRow:row];
1203   pkey     = [self->currentEntity primaryKeyForRow:row];
1204   
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"];
1209   }
1210   
1211   // lookup object in context/database
1212   object = [self->databaseContext objectForPrimaryKey:pkey
1213                                   entity:currentEntity];
1214   
1215   // use old, make new, clear fault
1216   if (object == nil) {
1217     //NSLog(@"new anObj\n");
1218     object = [self initializedObjectForRow:row
1219                    entity:currentEntity
1220                    zone:_zone];
1221   }
1222   if ([EOFault isFault:object]) {
1223     [EODatabaseFault clearFault:object];
1224     
1225     object = [object respondsToSelector:@selector(initWithPrimaryKey:entity:)]
1226       ? [object initWithPrimaryKey:row entity:currentEntity]
1227       : [object init];
1228     
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]];
1234     }
1235   }
1236     
1237   // make values
1238   // TODO - handle only class properties to object
1239   values = [NSMutableDictionary dictionaryWithCapacity:
1240                                   ([row count] + [currentRelations count])];
1241   [values addEntriesFromDictionary:row];
1242     
1243   // resolve relationships (to-one and to-many)
1244   {
1245     EORelationship *rel  = nil;
1246     int            i, n  = [self->currentRelations count];
1247     id             fault = nil;
1248         
1249     for (i = 0; i < n; i++) {
1250       rel = [self->currentRelations objectAtIndex:i];
1251             
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
1257                             relationship:rel];
1258         rel = nrel ? nrel : rel;
1259       }
1260       if ([rel isToMany]) {
1261         // Build to-many fault
1262         EOSQLQualifier* qualifier =
1263           [EOSQLQualifier qualifierForRow:row relationship:rel];
1264                 
1265         if (qualifier == nil) {
1266           // HH: THROW was uncommented ..
1267           [NSException raise:NSInvalidArgumentException
1268                        format:
1269                          @"Cannot build fault qualifier for relationship"];
1270           //    TODO    
1271           continue;
1272         }
1273
1274 #if LIB_FOUNDATION_LIBRARY
1275         if ([NSClassFromString([[rel destinationEntity] className])
1276                               isGarbageCollectable])
1277           fault = [EODatabaseFault gcArrayFaultWithQualifier:qualifier
1278                            fetchOrder:nil
1279                            databaseChannel:self
1280                            zone:_zone];
1281         else
1282 #endif
1283           fault = [EODatabaseFault arrayFaultWithQualifier:qualifier
1284                            fetchOrder:nil
1285                            databaseChannel:self
1286                            zone:_zone];
1287       }
1288       else {
1289         // Build to-one fault
1290         EOEntity     *faultEntity;
1291         NSDictionary *faultKey;
1292
1293         faultEntity = [rel         destinationEntity];
1294         faultKey    = [rel         foreignKeyForRow:row];
1295         faultKey    = [faultEntity primaryKeyForRow:faultKey];
1296                 
1297         if (faultEntity == nil) {
1298           [NSException raise:NSInvalidArgumentException
1299                        format:@"Cannot get entity for relationship"];
1300         }
1301         
1302         if (faultKey) {
1303           fault = [self->databaseContext objectForPrimaryKey:faultKey
1304                                          entity:faultEntity];
1305           if (fault == nil) {
1306             fault = [EODatabaseFault objectFaultWithPrimaryKey:faultKey
1307                                      entity:faultEntity
1308                                      databaseChannel:self
1309                                      zone:_zone];
1310             [databaseContext recordObject:fault 
1311                              primaryKey:faultKey
1312                              entity:faultEntity
1313                              snapshot:nil];
1314           }
1315         }
1316         else
1317           fault = [EONull null];
1318       }
1319             
1320       if (fault)
1321         [values setObject:fault forKey:[rel name]];
1322     }
1323   }
1324     
1325   // check if is updated in another context or just updated or new (delegate)
1326   dict = values;
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];
1334     }
1335     else {
1336       [NSException raise:NSInvalidArgumentException
1337                    format:@"object updated in an uncommitted transaction "
1338                      @"was fetched"];
1339     }
1340   }
1341   else {
1342     if ([delegate respondsToSelector:
1343            @selector(databaseChannel:willRefetchObject:fromSnapshot:)]) {
1344       dict = [delegate databaseChannel:self
1345                        willRefetchObject:object
1346                        fromSnapshot:values];
1347     }
1348   }
1349   // does delegate disallow setting the new values and recording the fetch ?
1350   if (dict == nil)
1351     return object;
1352   
1353   // put values
1354   [object takeValuesFromDictionary:dict];
1355   
1356   // register lock if locked
1357   if ([databaseContext updateStrategy] == EOUpdateWithPessimisticLocking)
1358     [databaseContext recordLockedObject:object];
1359     
1360   // register object in context
1361   [databaseContext recordObject:object
1362                    primaryKey:pkey
1363                    entity:currentEntity
1364                    snapshot:snapshot];
1365     
1366   // awake object from database channel
1367   if ([object respondsToSelector:@selector(awakeForDatabaseChannel:)])
1368     [object awakeForDatabaseChannel:self];
1369   
1370   // Done.
1371   return object;
1372 }
1373
1374 // ******************** Reporting errors ********************
1375
1376 - (void)privateReportError:(SEL)method :(NSString*)format,... {
1377   NSString* message;
1378   va_list va;
1379     
1380   if (![[databaseContext database] logsErrorMessages])
1381     return;
1382     
1383   va_start(va, format);
1384   message = AUTORELEASE([[NSString alloc] initWithFormat:format arguments:va]);
1385   va_end(va);
1386     
1387   [[databaseContext database]
1388                     reportErrorFormat:
1389                       @"EODatabaseChannel:error in [EODatabaseChannel %@]: %@",
1390                       NSStringFromSelector(method), message];
1391 }
1392
1393 @end /* EODatabaseChannel */
1394
1395 @implementation NSObject(EODatabaseChannelEONotifications)
1396
1397 - (BOOL)prepareForDeleteInChannel:(EODatabaseChannel *)_channel
1398   context:(EODatabaseContext *)_ctx
1399 {
1400   return YES;
1401 }
1402 - (void)wasDeletedInChannel:(EODatabaseChannel *)_channel
1403   context:(EODatabaseContext *)_ctx
1404 {
1405 }
1406
1407 - (BOOL)prepareForInsertInChannel:(EODatabaseChannel *)_channel
1408   context:(EODatabaseContext *)_ctx
1409 {
1410   return YES;
1411 }
1412 - (void)wasInsertedInChannel:(EODatabaseChannel *)_channel
1413   context:(EODatabaseContext *)_ctx
1414 {
1415 }
1416
1417 - (BOOL)prepareForUpdateInChannel:(EODatabaseChannel *)_channel
1418   context:(EODatabaseContext *)_ctx
1419 {
1420   return YES;
1421 }
1422 - (void)wasUpdatedInChannel:(EODatabaseChannel *)_channel
1423   context:(EODatabaseContext *)_ctx
1424 {
1425 }
1426
1427 - (BOOL)prepareForLockInChannel:(EODatabaseChannel *)_channel
1428   context:(EODatabaseContext *)_ctx
1429 {
1430   return YES;
1431 }
1432 - (void)wasLockedInChannel:(EODatabaseChannel *)_channel
1433   context:(EODatabaseContext *)_ctx
1434 {
1435 }
1436
1437 @end /* NSObject(EODatabaseChannelNotifications) */
1438
1439 @implementation EODatabaseChannel(Statistics)
1440
1441 - (unsigned int)successfulOpenCount {
1442   return self->successfulOpenCount;
1443 }
1444
1445 - (unsigned int)failedOpenCount {
1446   return self->failedOpenCount;
1447 }
1448
1449 - (unsigned int)closeCount {
1450   return self->closeCount;
1451 }
1452
1453 - (unsigned int)insertCount {
1454   return self->insertCount;
1455 }
1456
1457 - (unsigned int)updateCount {
1458   return self->updateCount;
1459 }
1460
1461 - (unsigned int)deleteCount {
1462   return self->deleteCount;
1463 }
1464
1465 - (unsigned int)lockCount {
1466   return self->lockCount;
1467 }
1468
1469 @end