]> err.no Git - sope/blob - sope-gdl1/FrontBase2/FBChannel.m
added missing inline pathes
[sope] / sope-gdl1 / FrontBase2 / FBChannel.m
1 /* 
2    FBChannel.m
3
4    Copyright (C) 1999 MDlink online service center GmbH and Helge Hess
5
6    Author: Helge Hess (helge.hess@mdlink.de)
7
8    This file is part of the FB Adaptor Library
9
10    This library is free software; you can redistribute it and/or
11    modify it under the terms of the GNU Library General Public
12    License as published by the Free Software Foundation; either
13    version 2 of the License, or (at your option) any later version.
14
15    This library is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18    Library General Public License for more details.
19
20    You should have received a copy of the GNU Library General Public
21    License along with this library; see the file COPYING.LIB.
22    If not, write to the Free Software Foundation,
23    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 */
25 // $Id: FBChannel.m 1 2004-08-20 10:38:46Z znek $
26
27 #include <ctype.h>
28 #include <string.h>
29 #if HAVE_STRINGS_H
30 #include <strings.h>
31 #endif
32 #import "common.h"
33 #include "FBBlobHandle.h"
34 #include <NGExtensions/NSString+misc.h>
35
36 #include <FBCAccess/FBCDigestPassword.h>
37 #include <GDLAccess/EORecordDictionary.h>
38 #import <EOControl/EOSortOrdering.h>
39
40 //#define BLOB_DEBUG 1
41
42 @implementation FrontBaseChannel
43
44 static EONull *null = nil;
45
46 + (void)initialize {
47   if (null == NULL) null = [[EONull null] retain];
48 }
49
50 #ifdef __MINGW32__
51 extern __declspec(import) void fbcInitialize(void);
52 #endif
53
54 - (id)initWithAdaptorContext:(EOAdaptorContext*)_adaptorContext {
55   static BOOL didInit = NO;
56   if (!didInit) {
57     didInit = YES;
58 #ifdef __MINGW32__
59     fbcInitialize();
60 #endif
61 #ifdef __APPLE__
62     fbcInitialize();
63 #endif
64   }
65   
66   if ((self = [super initWithAdaptorContext:_adaptorContext])) {
67     [self setDebugEnabled:[[NSUserDefaults standardUserDefaults]
68                                            boolForKey:@"FBDebugEnabled"]];
69     sqlLogFile = [[[NSUserDefaults standardUserDefaults]
70                                    stringForKey:@"FBLogFile"]
71                                    copyWithZone:[self zone]];
72     self->_primaryKeysNamesForTableName = [[NSMutableDictionary alloc] init];
73     self->_attributesForTableName       = [[NSMutableDictionary alloc] init];
74   }
75   return self;
76 }
77
78 #if !LIB_FOUNDATION_BOEHM_GC
79 - (void)dealloc {
80   if ([self isOpen]) {
81     [self cancelFetch];
82     [self closeChannel];
83   }
84   RELEASE(self->sqlLogFile);
85   RELEASE(self->_primaryKeysNamesForTableName);
86   RELEASE(self->_attributesForTableName);
87   [super dealloc];
88 }
89 #endif
90
91 // NSCopying methods 
92
93 - (id)copyWithZone:(NSZone *)zone {
94   return RETAIN(self);// copy is needed during creation of NSNotification object
95 }
96
97 // debugging
98
99 - (void)setDebugEnabled:(BOOL)_flag {
100   self->isDebuggingEnabled = _flag;
101 }
102 - (BOOL)isDebugEnabled {
103   return self->isDebuggingEnabled;
104 }
105
106 // open/close
107
108 - (BOOL)isOpen {
109   return self->fbdc != NULL ? YES : NO;
110 }
111
112 - (BOOL)openChannel {
113   FrontBase2Adaptor *adaptor;
114   FBCMetaData *md;
115   BOOL        result = NO;
116   NSString    *txIsoLevel;
117   NSString    *locking;
118   NSString    *expr;
119   const char  *password;
120   char        pwdDigest[17];
121
122   //  [self setDebugEnabled:YES];
123
124   adaptor = (FrontBase2Adaptor *)[[self adaptorContext] adaptor];
125   
126   if (![super openChannel])
127     return NO;
128
129   /* digest the database password */
130
131   if ((password = [[adaptor databasePassword] cString])) {
132     if (fbcDigestPassword("_SYSTEM", (char *)password, pwdDigest) == NULL) {
133       NSLog(@"%@: Couldn't digest password !", self);
134       pwdDigest[0] = '\0';
135     }
136   }
137   else
138     pwdDigest[0] = '\0';
139   
140   /* connect to database */
141
142   if (isDebuggingEnabled)
143     NSLog(@"Open fb-channel[%p]: db=%@, server=%@", self,
144           [adaptor databaseName], [adaptor serverName]);
145   
146   self->fbdc =
147     fbcdcConnectToDatabase((char *)[[adaptor databaseName]     cString],
148                            (char *)[[adaptor serverName]       cString],
149                            (char *)pwdDigest);
150   if (self->fbdc == NULL) {
151     if (isDebuggingEnabled) {
152       NSLog(@"FrontBase channel 0x%08X (db=%@, server=%@, digest=%s) "
153             @"could not be opened: %s ..", self,
154             [adaptor databaseName], [adaptor serverName],
155             pwdDigest /* ? "yes" : "no"*/,
156             fbcdcClassErrorMessage());
157     }
158     return NO;
159   }
160   
161   fbcdcSetRollbackAfterError(self->fbdc, True);
162
163   /* digest the user password */
164 #if 0
165   if ((password = [[adaptor loginPassword] cString])) {
166     if (fbcDigestPassword((char *)[[adaptor loginName] cString],
167                           (char *)password, pwdDigest) == NULL) {
168       NSLog(@"%@: Couldn't digest password !", self);
169       pwdDigest[0] = '\0';
170     }
171   }
172   else
173     pwdDigest[0] = '\0';
174 #else
175   if (!(password = [[adaptor loginPassword] cString]))
176     password = "\0";
177 #endif
178   
179   /* create session */
180
181   md = fbcdcCreateSession(self->fbdc,
182                           (char *)[[NSString stringWithFormat:
183                                                @"GDL<0x%08X>", self]
184                                              cString],
185                           (char *)[[adaptor loginName] cString],
186                           //                          pwdDigest,
187                           (char *)password,                          
188                           (char *)[NSUserName() cString]);
189   if (md == NULL) {
190     if (isDebuggingEnabled) {
191       NSLog(@"FrontBase session (channel=0x%08X) couldn't be created, "
192             @" login=%@ password=%s user=%@: %s.",
193             self,
194             [adaptor loginName], [adaptor loginPassword],
195             [adaptor loginName],
196             fbcdcClassErrorMessage());
197     }
198     fbcdcClose(self->fbdc); self->fbdc = NULL;
199     return NO;
200   }
201
202   if (fbcmdErrorCount(md) > 0) {
203     if (isDebuggingEnabled) {
204       FBCErrorMetaData *emd;
205       int i, count;
206       
207       emd = fbcdcErrorMetaData(self->fbdc, md);
208
209       NSLog(@"FrontBase session (channel=0x%08X) couldn't be created:", self);
210       
211       for (i = 0, count = fbcemdErrorCount(emd); i < count; i++) {
212         unsigned   code;
213         const char *errorKind;
214         const char *emsg;
215
216         code      = fbcemdErrorCodeAtIndex(emd, i);
217         errorKind = fbcemdErrorKindAtIndex(emd, i);
218         emsg      = fbcemdErrorMessageAtIndex(emd, i);
219
220         NSLog(@"  code=%i", code);
221         NSLog(@"  kind=%s", errorKind);
222         NSLog(@"  msdg=%s", emsg);
223 #if 0        
224         free((void *)errorKind);
225 #endif        
226         free((void *)emsg);
227       }
228       fbcemdRelease(emd); emd = NULL;
229     }
230     fbcdcClose(self->fbdc); self->fbdc = NULL;
231     fbcmdRelease(md); md = NULL;
232     return NO;
233   }
234   
235   fbcmdRelease(md); md = NULL;
236
237   result = YES;
238
239   /* apply session level setting (locking & tx levels) */
240
241   if ((txIsoLevel = [adaptor transactionIsolationLevel]) == nil)
242     txIsoLevel = @"READ COMMITTED";
243   if ((locking = [adaptor lockingDiscipline]) == nil)
244     locking = @"OPTIMISTIC";
245
246   expr = [NSString stringWithFormat:
247                      @"SET TRANSACTION ISOLATION LEVEL %s, LOCKING %s",
248                      [txIsoLevel cString], [locking cString]];
249   if (![self evaluateExpression:expr]) {
250     if (isDebuggingEnabled)
251       NSLog(@"%@: couldn't apply tx iso level '%@' and locking '%@'..",
252             self, txIsoLevel, locking);
253   }
254
255 #if LIB_FOUNDATION_BOEHM_GC
256   [GarbageCollector registerForFinalizationObserver:self
257                     selector:@selector(_adaptorWillFinalize:)
258                     object:[[self adaptorContext] adaptor]];
259 #endif
260
261   if (isDebuggingEnabled)
262     NSLog(@"FrontBase channel 0x%08X opened ..", self);
263   
264   return result;
265 }
266
267 - (void)primaryCloseChannel {
268   /* release all resources */
269   
270   if (self->fbdc) {
271     if (isDebuggingEnabled) {
272       id ad;
273
274       ad = [[self adaptorContext] adaptor];
275       NSLog(@"Close fb-channel[%p]: db=%@, server=%@", self,
276             ad, [ad serverName]);
277     }
278
279     fbcdcClose(self->fbdc);
280     self->fbdc = NULL;
281   }
282   
283   RELEASE(self->selectedAttributes); self->selectedAttributes = nil;
284 }
285
286 - (void)closeChannel {
287   [super closeChannel];
288   [self primaryCloseChannel];
289 }
290
291 // fetching rows
292
293 - (void)cancelFetch {
294   if (![self isOpen]) {
295     [FrontBaseException raise:@"ChannelNotOpenException"
296                         format:@"No fetch in progress, fb connection is not"
297                            @" open (channel=%@)", self];
298   }
299
300   if (self->fetchHandle) {
301     FBCMetaData *md;
302     if ((md = fbcdcCancelFetch(self->fbdc, self->fetchHandle))) {
303       fbcmdRelease(md); md = NULL;
304     }
305     self->fetchHandle = NULL;
306   }
307
308   if (self->datatypeCodes) {
309     free(self->datatypeCodes);
310     self->datatypeCodes = NULL;
311   }
312
313   if (self->rowHandler) {
314     fbcrhRelease(self->rowHandler);
315     self->rowHandler = NULL;
316     self->rawRows    = NULL;
317   }
318   else {
319     NSAssert(self->rawRows == NULL, @"raw rows set, but no row handler ! ..");
320   }
321   
322   if (self->cmdMetaData) {
323     fbcmdRelease(self->cmdMetaData);
324     self->cmdMetaData = NULL;
325   }
326
327   NSAssert(self->rowHandler == NULL, @"row handler still set ..");
328   NSAssert(self->rawRows    == NULL, @"raw row still set ..");
329   
330   [super cancelFetch];
331
332   self->currentRow      = 0;
333   self->numberOfColumns = 0;
334   RELEASE(self->selectedAttributes); self->selectedAttributes = nil;
335 }
336
337 - (NSArray *)describeResults {
338   int                 cnt;
339   NSMutableArray      *result    = nil;
340   NSMutableDictionary *usedNames = nil;
341   NSNumber            *yesObj    = [NSNumber numberWithBool:YES];
342
343   if (![self isFetchInProgress]) {
344     [FrontBaseException raise:@"NoFetchInProgress"
345                         format:@"No fetch in progress (channel=%@)", self];
346   }
347
348   NSAssert(self->cmdMetaData, @"no cmd-meta-data ..");
349
350   result    =
351     [[NSMutableArray alloc] initWithCapacity:self->numberOfColumns + 1];
352   usedNames =
353     [[NSMutableDictionary alloc] initWithCapacity:self->numberOfColumns + 1];
354
355   for (cnt = 0; cnt < self->numberOfColumns; cnt++) {
356     const FBCColumnMetaData *cmd;
357     EOAttribute *attribute  = nil;
358     NSString    *columnName = nil;
359     NSString    *attrName   = nil;
360
361     cmd = fbcmdColumnMetaDataAtIndex(self->cmdMetaData,   cnt);
362     
363     if (cmd) {
364       const char *s;
365
366       if ((s = fbccmdLabelName(cmd)))
367         columnName = [NSString stringWithCString:s];
368       else if ((s = fbccmdColumnName(cmd)))
369         columnName = [NSString stringWithCString:s];
370     }
371
372     if ([columnName length] == 0) {
373       columnName = [NSString stringWithFormat:@"column%i", cnt];
374       attrName   = [NSString stringWithFormat:@"column%i", cnt];
375     }
376     else
377       attrName = [columnName _sybModelMakeInstanceVarName];
378
379     if ([[usedNames objectForKey:attrName] boolValue]) {
380       int      cnt2 = 0;
381       char     buf[64];
382       NSString *newAttrName = nil;
383
384       for (cnt2 = 2; cnt2 < 100; cnt2++) {
385         sprintf(buf, "%i", cnt2);
386         
387         newAttrName = [attrName stringByAppendingString:
388                                   [NSString stringWithCString:buf]];
389         
390         if (![[usedNames objectForKey:newAttrName] boolValue]) {
391           attrName = newAttrName;
392           break;
393         }
394       }
395     }
396     [usedNames setObject:yesObj forKey:attrName];
397
398     attribute = [[EOAttribute alloc] init];
399     [attribute setName:attrName];
400     [attribute setColumnName:columnName];
401     [attribute loadValueClassAndTypeFromFrontBaseType:self->datatypeCodes[cnt]];
402     [result addObject:attribute];
403     RELEASE(attribute); attribute = nil;
404   }
405
406   RELEASE(usedNames);
407   usedNames = nil;
408   
409   return AUTORELEASE(result);
410 }
411
412 - (int)batchSize {
413   return 1000;
414 }
415
416 - (BOOL)_nextBatch {
417   if (self->rowHandler) {
418     fbcrhRelease(self->rowHandler);
419     self->rowHandler = NULL;
420     self->rawRows = NULL;
421   }
422
423   //NSLog(@"fetching new batch ..");
424   NSAssert(self->fetchHandle, @"missing fetch handle ..");
425   self->rawRows = fbcdcFetch(self->fbdc, [self batchSize], self->fetchHandle);
426
427   if (self->rawRows) {
428     self->rowHandler = fbcrhInitWith(self->rawRows, self->cmdMetaData);
429     self->isFirstInBatch = YES;
430     //NSLog(@"fetched new batch %i rows ..", fbcrhRowCount(self->rowHandler));
431   }
432   else {
433     /* no more fetch data, finish fetch op */
434     [self cancelFetch];
435     self->isFetchInProgress = NO;
436     return NO;
437   }
438
439   return YES;
440 }
441
442 - (NSMutableDictionary *)primaryFetchAttributes:(NSArray *)_attributes
443   withZone:(NSZone *)_zone
444 {
445   NSMutableDictionary *record;
446   void **rawRow;
447   int  i;
448
449   record = nil;
450   
451   if (!self->isFetchInProgress)
452     return nil;
453
454   if (self->rawRows == NULL) {
455     if (![self _nextBatch])
456       return nil;
457   }
458     
459   if (self->selectedAttributes == nil) {
460     self->selectedAttributes = RETAIN([self describeResults]);
461   }
462
463   if ([_attributes count] < [self->selectedAttributes count])
464     _attributes = self->selectedAttributes;
465   
466   NSAssert(self->rowHandler, @"missing row handler ..");
467
468   rawRow = self->isFirstInBatch
469     ? fbcrhFirstRow(self->rowHandler)
470     : fbcrhNextRow(self->rowHandler);
471
472   if (rawRow == NULL) {
473     /* no more rows available in fetch-buffer, get next batch */
474     if (![self _nextBatch])
475       return nil;
476
477     rawRow = self->isFirstInBatch
478       ? fbcrhFirstRow(self->rowHandler)
479       : fbcrhNextRow(self->rowHandler);
480   }
481   self->isFirstInBatch = NO;
482
483   if (rawRow == NULL) {
484     /* no rows were in batch and no new batch could be fetched, so we finished */
485     [self cancelFetch];
486     self->isFetchInProgress = NO;
487     return nil;
488   }
489
490   /* deconstruct raw row */
491
492   {
493     id objects[self->numberOfColumns];
494     id keys[self->numberOfColumns];
495     
496     for (i = 0; i < self->numberOfColumns; i++) {
497       EOAttribute *attribute;
498       register void *rawValue;
499       Class    valueClass;
500       NSString *attrName;
501       id value;
502
503       objects[i] = NULL;
504       keys[i]    = NULL;
505       
506       if (self->datatypeCodes[i] < 1)
507         continue;
508
509 #if 1
510       if (!(attribute  = [_attributes objectAtIndex:i])) {
511         attribute  = [self->selectedAttributes objectAtIndex:i];
512       }
513 #endif
514       
515       attrName   = [attribute name];
516       valueClass = NSClassFromString([attribute valueClassName]);
517
518 #if DEBUG
519       NSAssert3(valueClass,
520                 @"no valueClass for attribute %@ entity %@ name %@ ..",
521                 attribute, [attribute entity], [attribute valueClassName]);
522 #endif
523     
524       if (attrName == nil)
525         attrName = [NSString stringWithFormat:@"column%i", i];
526
527       rawValue = rawRow[i];
528       if (rawValue == NULL) {
529         value = null;
530       }
531       else {
532         FBCBlobHandle *handle = NULL;
533         unsigned int len = 4;
534       
535         switch (self->datatypeCodes[i]) {
536           case FB_Boolean:
537             len = sizeof(unsigned char);
538             break;
539           
540           case FB_Character:
541             len = strlen(rawValue);
542             break;
543           case FB_VCharacter:
544             len = strlen(rawValue);
545             break;
546           case FB_Date:
547             NSLog(@"handling date ..");
548             len = strlen(rawValue);
549             break;
550           case FB_Timestamp:
551             NSLog(@"handling timestamp ..");
552             len = strlen(rawValue);
553             break;
554           case FB_TimestampTZ:
555             len = strlen(rawValue);
556             if (len != 25) {
557               NSLog(@"handling timestamp with TZ with length=%i ..", len);
558             }
559             break;
560
561           case FB_SmallInteger:
562             len = sizeof(short);
563             break;
564           
565           case FB_Integer:
566           case FB_YearMonth:
567             len = sizeof(int);
568             break;
569
570           case FB_Double:
571           case FB_Real:
572           case FB_Numeric:
573           case FB_Decimal:
574           case FB_DayTime:
575             len = sizeof(double);
576             break;
577
578           case FB_Float:
579             len = sizeof(float);
580             break;
581
582           case FB_CLOB:
583           case FB_BLOB:
584             /*
585               CLOB/BLOB are a bit more tricky, mainly because values of up to a
586               given size are inlined in the fetch result, i.e. no need for a
587               round trip to the server. 
588             */
589             if ((*(unsigned char *)rawValue) == 0) {
590               /* blob via handle */
591 #if BLOB_DEBUG
592               NSLog(@"blob via handle %s",
593                     ((FBCBlobIndirect *)rawValue)->handleAsString);
594 #endif
595
596               handle =
597                 fbcbhInitWithHandle(((FBCBlobIndirect *)rawValue)->handleAsString);
598               len    = fbcbhBlobSize(handle);
599
600               rawValue = fbcdcReadBLOB(self->fbdc, handle);
601             }
602             else {
603               /* blob inline */
604               FBCBlobDirect *blob;
605
606 #if BLOB_DEBUG
607               NSLog(@"blob inline");
608 #endif
609               blob     = rawValue;
610               rawValue = blob->blobData;
611               len      = blob->blobSize;
612             }
613 #if BLOB_DEBUG
614             NSLog(@"  size=%d", len);
615 #endif
616             break;
617         }
618
619         /* make an object value from the data */
620
621 #if DEBUG
622         NSAssert3(valueClass,
623                   @"lost valueClass for attribute %@ entity %@ name %@ ..",
624                   attribute, [attribute entity], [attribute valueClassName]);
625 #endif
626       
627         value = [valueClass valueFromBytes:rawValue length:len
628                             frontBaseType:self->datatypeCodes[i]
629                             attribute:attribute
630                             adaptorChannel:self];
631
632 #if DEBUG
633         if (value == nil) {
634           NSAssert4(value,
635                     @"no value for attribute %@ entity %@ "
636                     @"valueClass %@, record %@ ..",
637                     attribute, [attribute entity],
638                     NSStringFromClass(valueClass), record);
639         }
640 #endif
641       
642         /* free BLOB handle if one was allocated */
643       
644         if (handle) {
645           fbcbhRelease(handle);
646           handle = NULL;
647         }
648       }
649
650 #if DEBUG
651       NSAssert2(value, @"no value for attribute %@ entity %@ ..",
652                 attribute, [attribute entity]);
653 #endif
654
655       objects[i] = value;
656       keys[i]    = [attribute name];
657     }
658     {
659       static Class EORecordDictionaryClass = nil;
660
661       if (EORecordDictionaryClass == nil)
662         EORecordDictionaryClass = [EORecordDictionary class];
663       
664       record = (id)NSAllocateObject(EORecordDictionaryClass,
665                                 sizeof(EORecordDictionary) *
666                                 self->numberOfColumns, nil);
667       record = [record initWithObjects:objects forKeys:keys
668                        count:self->numberOfColumns];
669       AUTORELEASE(record);
670     }
671   }
672   return record;
673 }
674
675 /* sending sql to server */
676
677 - (BOOL)evaluateExpression:(NSString *)_expression {
678   BOOL result;
679 #if DEBUG
680   static Class NSDateClass = Nil;
681   NSDate *startDate;
682 #endif
683
684   *(&result) = YES;
685   NSAssert(self->rowHandler  == NULL, @"raw handler still available ..");
686   NSAssert(self->rawRows     == NULL, @"raw rows still available ..");
687   NSAssert(self->fetchHandle == NULL, @"fetch handle still available ..");
688
689   if (_expression == nil) {
690     [InvalidArgumentException raise:@"InvalidArgumentException"
691                               format:@"parameter for evaluateExpression: "
692                                 @"must not be null (channel=%@)", self];
693   }
694   
695   *(&_expression) = AUTORELEASE([_expression mutableCopy]);
696
697   if (delegateRespondsTo.willEvaluateExpression) {
698     EODelegateResponse response =
699       [delegate adaptorChannel:self
700                 willEvaluateExpression:(NSMutableString *)_expression];
701     
702     if (response == EODelegateRejects)
703       return NO;
704     else if (response == EODelegateOverrides)
705       return YES;
706   }
707
708   if (isDebuggingEnabled)
709     NSLog(@"SQL[%p]: %@", self, _expression);
710   
711
712   if (![self isOpen]) {
713     [FrontBaseException raise:@"ChannelNotOpenException"
714                        format:@"FrontBase connection is not open (channel=%@)",
715                          self];
716   }
717   if (self->cmdMetaData != NULL) {
718     [FrontBaseException raise:@"CommandInProgressException"
719                         format:@"command data already set up"
720                           @" (channel=%@)", self];
721   }
722   
723   _expression = [_expression stringByAppendingString:@";"];
724   
725   if (self->sqlLogFile) {
726     FILE *fh;
727     if ((fh = fopen([self->sqlLogFile cString], "a"))) {
728       fprintf(fh, "%s\n", [_expression cString]);
729       fflush(fh);
730       fclose(fh);
731     }
732   }
733   
734   /* execute expression */
735
736 #if DEBUG
737   if (NSDateClass == Nil) NSDateClass = [NSDate class];
738   startDate = [NSDateClass date];
739 #endif
740   
741   self->cmdMetaData = fbcdcExecuteDirectSQL(self->fbdc,
742                                             (char *)[_expression cString]);
743   if (self->cmdMetaData == NULL) {
744     NSLog(@"%@: could not execute SQL: %@", self, _expression);
745     return NO;
746   }
747
748   /* check for errors */
749   
750   if (fbcmdErrorCount(self->cmdMetaData) > 0) {
751     FBCErrorMetaData *emd;
752     char *msg;
753     NSString *error;
754     int i, count;
755
756     emd = fbcdcErrorMetaData(self->fbdc, self->cmdMetaData);
757     
758     for (i = 0, count = fbcemdErrorCount(emd); i < count; i++) {
759       unsigned   code;
760       const char *errorKind;
761       const char *emsg;
762
763       code      = fbcemdErrorCodeAtIndex(emd, i);
764       errorKind = fbcemdErrorKindAtIndex(emd, i);
765       emsg      = fbcemdErrorMessageAtIndex(emd, i);
766
767       NSLog(@"  code=%i", code);
768       NSLog(@"  kind=%s", errorKind);
769       NSLog(@"  msdg=%s", emsg);
770 #if 0
771       free((void *)errorKind);
772 #endif
773       free((void *)emsg);
774     }
775     
776     if ((msg = fbcemdAllErrorMessages(emd)))
777       error = [NSString stringWithCString:msg];
778     else
779       error = @"unknown";
780     free(msg); msg = NULL;
781     fbcemdRelease(emd); emd = NULL;
782     fbcmdRelease(self->cmdMetaData); self->cmdMetaData = NULL;
783     
784     if (self->sqlLogFile) {
785       FILE *fh;
786       if ((fh = fopen([self->sqlLogFile cString], "a"))) {
787         fprintf(fh, "# failed: %s\n",
788                 [[error stringByApplyingCEscaping] cString]);
789         fflush(fh);
790         fclose(fh);
791       }
792     }
793     
794     NSLog(@"%@: could not execute SQL: %@\n  reason: %@",
795           self, _expression,
796           error);
797     return NO;
798   }
799
800   /* init common results */
801
802   NSAssert(self->cmdMetaData, @"missing command-meta-data");
803   self->numberOfColumns   = fbcmdColumnCount(self->cmdMetaData);
804   self->rowsAffected      = fbcmdRowCount(self->cmdMetaData);
805   self->txVersion         = fbcmdTransactionVersion(self->cmdMetaData);
806   self->isFetchInProgress = NO;
807
808   if ((self->fetchHandle = fbcmdFetchHandle(self->cmdMetaData)))
809     self->isFetchInProgress = YES;
810
811   if (self->numberOfColumns > 0) {
812     int i;
813     
814     self->datatypeCodes = calloc(self->numberOfColumns, sizeof(int));
815     for (i = 0; i < self->numberOfColumns; i++) {
816       const FBCDatatypeMetaData *dmd;
817
818       if ((dmd = fbcmdDatatypeMetaDataAtIndex(self->cmdMetaData, i)))
819         self->datatypeCodes[i] = fbcdmdDatatypeCode(dmd);
820       else
821         self->datatypeCodes[i] = FB_VCharacter;
822     }
823   }
824
825   if (!self->isFetchInProgress) {
826     fbcmdRelease(self->cmdMetaData);
827     self->cmdMetaData = NULL;
828   }
829 #if DEBUG
830   else {
831     /* some constraints */
832     if ([_expression hasPrefix:@"INSERT"]) {
833       NSLog(@"an insert shouldn't start a fetch ! ..");
834     }
835     else if ([_expression hasPrefix:@"DELETE"]) {
836       NSLog(@"a delete shouldn't start a fetch ! ..");
837     }
838     else if ([_expression hasPrefix:@"UPDATE"]) {
839       NSLog(@"an update shouldn't start a fetch ! ..");
840     }
841   }
842 #endif
843
844   /* setup row handler */
845   
846   if (result) {
847     if (delegateRespondsTo.didEvaluateExpression)
848       [delegate adaptorChannel:self didEvaluateExpression:_expression];
849   }
850   else {
851     [self cancelFetch];
852   }
853
854   if (self->sqlLogFile) {
855     FILE *fh;
856     if ((fh = fopen([self->sqlLogFile cString], "a"))) {
857 #if DEBUG
858       fprintf(fh, "# %s %3.3g\n", result ? "yes" : "no",
859               -[startDate timeIntervalSinceNow]);
860 #else
861       fprintf(fh, "# %s\n", result ? "yes" : "no");
862 #endif
863       fflush(fh);
864       fclose(fh);
865     }
866   }
867
868   return result;
869 }
870
871 /* description */
872
873 - (NSString *)description {
874   return [NSString stringWithFormat:@"<%@[0x%08X]: open=%s fetching=%s>",
875                      NSStringFromClass([self class]),
876                      self,
877                      [self isOpen] ? "yes" : "no",
878                      [self isFetchInProgress] ? "yes" : "no"];
879 }
880
881 @end /* FrontBaseChannel */
882
883 @implementation FrontBaseChannel(PrimaryKeyGeneration)
884
885 - (NSDictionary *)primaryKeyForNewRowWithEntity:(EOEntity *)_entity {
886   NSArray          *pkeys;
887   FrontBase2Adaptor *adaptor;
888   NSString         *newKeyExpr;
889   NSDictionary     *pkey;
890
891   pkeys      = [_entity primaryKeyAttributeNames];
892   adaptor    = (id)[[self adaptorContext] adaptor];
893   newKeyExpr = [adaptor newKeyExpression];
894
895   if (newKeyExpr == nil) {
896     NSLog(@"ERROR: missing newkey expression, can't gen pkey for %@",
897           [_entity name]);
898     return nil;
899   }
900
901   if ([pkeys count] != 1) {
902     NSLog(@"no pkeys configured for entity %@", [_entity name]);
903     return nil;
904   }
905
906   *(&pkey) = nil;
907
908   NS_DURING {
909     if ([self evaluateExpression:newKeyExpr]) {
910       NSDictionary *row;
911       NSArray *attrs;
912       id key;
913
914       attrs = [self describeResults];
915       row = [self fetchAttributes:attrs withZone:NULL];
916       [self cancelFetch];
917
918       if ((key = [[row objectEnumerator] nextObject])) {
919         pkey = [NSDictionary dictionaryWithObject:
920                                [NSNumber numberWithInt:[key intValue]]
921                              forKey:[pkeys objectAtIndex:0]];
922       }
923     }
924     else {
925       NSLog(@"could not evaluate newkey expression: %@", newKeyExpr);
926     }
927   }
928   NS_HANDLER {
929     fprintf(stderr, "newkey failed: %s\n",
930             [[localException description] cString]);
931     fflush(stderr);
932     pkey = nil;
933   }
934   NS_ENDHANDLER;
935   
936   return pkey;
937 }
938
939 @end /* FrontBaseChannel(PrimaryKeyGeneration) */
940
941 @implementation FrontBaseChannel(BlobHandling)
942
943 - (NSDictionary *)_extractBlobsFromRow:(NSMutableDictionary *)_mrow
944   entity:(EOEntity *)_entity
945 {
946   /*
947     Search for BLOB attributes.
948     If a BLOB attribute is found, remove it from mrow and add it's data to
949     blobRow.
950   */
951   FrontBase2Adaptor    *adaptor;
952   NSMutableDictionary *blobRow;
953   NSEnumerator *keys;
954   NSString     *key;
955
956   adaptor = (FrontBase2Adaptor *)[[self adaptorContext] adaptor];
957 #if DEBUG
958   NSAssert(adaptor, @"missing adaptor ..");
959 #endif
960
961   /* must use allKeys since we are going to modify mrow */
962   keys = [[_mrow allKeys] objectEnumerator];
963   blobRow = nil;
964
965   while ((key = [keys nextObject])) {
966     EOAttribute *attribute;
967     int fbType;
968
969     attribute = [_entity attributeNamed:key];
970     fbType = [adaptor typeCodeForExternalName:[attribute externalType]];
971
972     if ((fbType == FB_BLOB) || (fbType == FB_CLOB)) {
973       NSData *data;
974       id value;
975
976       value = [_mrow objectForKey:key];
977       data  = [value dataValueForFrontBaseType:fbType
978                      attribute:attribute];
979       if (data == nil) // EONull
980         continue;
981         
982       if (blobRow == nil)
983         blobRow = [NSMutableDictionary dictionaryWithCapacity:4];
984
985       [blobRow setObject:data forKey:key];
986       [_mrow   removeObjectForKey:key];
987     }
988     else if (fbType == FB_VCharacter) {
989       /* check for large VARCHARs and handle them like BLOBs.. */
990       id value;
991       
992       value = [_mrow objectForKey:key];
993       
994       if ([value isKindOfClass:[NSString class]]) {
995         if ([value length] > 2000) {
996           NSData *data;
997
998           data = [value dataValueForFrontBaseType:fbType
999                         attribute:attribute];
1000           if (data == nil)
1001             continue;
1002
1003           if (blobRow == nil)
1004             blobRow = [NSMutableDictionary dictionaryWithCapacity:4];
1005
1006           [blobRow setObject:data forKey:key];
1007           [_mrow   removeObjectForKey:key];
1008         }
1009       }
1010       else if ([value isKindOfClass:[NSData class]]) {
1011         if ([value length] > 60) {
1012           NSData *data;
1013
1014           data = [value dataValueForFrontBaseType:fbType
1015                         attribute:attribute];
1016           if (data == nil)
1017             continue;
1018
1019           if (blobRow == nil)
1020             blobRow = [NSMutableDictionary dictionaryWithCapacity:4];
1021
1022           [blobRow setObject:data forKey:key];
1023           [_mrow   removeObjectForKey:key];
1024         }
1025       }
1026     }
1027 #if 0
1028     else {
1029       NSLog(@"%@ is *not* a BLOB attribute (type=%i) ..", attribute, fbType);
1030     }
1031 #endif
1032   }
1033   return blobRow;
1034 }
1035
1036 - (BOOL)_writeBlobs:(NSDictionary *)_blobs ofRow:(NSMutableDictionary *)_mrow {
1037   /*
1038     This method writes the blobs and set's the appropriate handle-keys in
1039     the row.
1040   */
1041   if (_blobs) {
1042     NSEnumerator *keyEnum;
1043     NSString *key;
1044
1045     keyEnum = [_blobs keyEnumerator];
1046     while ((key = [keyEnum nextObject])) {
1047       NSData        *value;
1048       FBCBlobHandle *blob;
1049       
1050       value = [_blobs objectForKey:key];
1051
1052 #if BLOB_DEBUG
1053       NSLog(@"writing blob %@ of size %i ...", key, [value length]);
1054 #endif
1055       
1056       if ((blob = fbcdcWriteBLOB(self->fbdc,
1057                                  (char*)[value bytes],
1058                                  [value length]))) {
1059         char     blobid[FBBlobHandleByteSize + 4];
1060         NSString *blobKey;
1061         FBBlobHandle *handle;
1062         
1063         fbcbhGetHandle(blob, blobid);
1064         
1065         blobKey = [NSString stringWithCString:blobid
1066                             length:FBBlobHandleByteSize + 3];
1067 #if BLOB_DEBUG
1068         NSLog(@"  got id %@ for blob %@ ...", blobKey, key);
1069 #endif
1070         if (blobKey == nil) {
1071           if (blob)
1072             fbcbhRelease(blob);
1073           return NO;
1074         }
1075
1076         fbcbhRelease(blob); blob = NULL;
1077
1078         handle = [[FBBlobHandle alloc] initWithBlobID:blobKey];
1079         [_mrow setObject:handle forKey:key];
1080         RELEASE(handle); handle = nil;
1081       }
1082       else {
1083         NSLog(@"%@: writing of BLOB %@ failed !", self, key);
1084         return NO;
1085       }
1086     }
1087     return YES;
1088   }
1089   else
1090     return YES;
1091 }
1092
1093 - (BOOL)insertRow:(NSDictionary *)row forEntity:(EOEntity *)entity {
1094   EOSQLExpression     *sqlexpr     = nil;
1095   NSMutableDictionary *mrow        = nil;
1096   NSDictionary        *blobRow     = nil;
1097
1098   mrow = AUTORELEASE([row mutableCopyWithZone:[row zone]]);
1099   
1100   if (!isOpen)
1101     [[ChannelIsNotOpenedException new] raise];
1102
1103   if((row == nil) || (entity == nil)) {
1104     [[[InvalidArgumentException alloc]
1105              initWithFormat:@"row and entity arguments for insertRow:forEntity:"
1106                             @"must not be the nil object"] raise];
1107   }
1108
1109   if([self isFetchInProgress])
1110     [[AdaptorIsFetchingException exceptionWithAdaptor:self] raise];
1111
1112   if(![adaptorContext transactionNestingLevel])
1113     [[NoTransactionInProgressException exceptionWithAdaptor:self] raise];
1114
1115   if(delegateRespondsTo.willInsertRow) {
1116     EODelegateResponse response;
1117
1118     response = [delegate adaptorChannel:self
1119                          willInsertRow:mrow
1120                          forEntity:entity];
1121     if(response == EODelegateRejects)
1122       return NO;
1123     else if(response == EODelegateOverrides)
1124       return YES;
1125   }
1126
1127   /* extract BLOB attributes */
1128
1129   blobRow = [self _extractBlobsFromRow:mrow entity:entity];
1130   
1131   /* upload BLOBs */
1132
1133   if (![self _writeBlobs:blobRow ofRow:mrow])
1134     return NO;
1135   
1136   /* insert non-BLOB attributes */
1137   
1138   sqlexpr = [[[adaptorContext adaptor]
1139                               expressionClass]
1140                               insertExpressionForRow:mrow
1141                               entity:entity
1142                               channel:self];
1143     
1144   if(![self evaluateExpression:[sqlexpr expressionValueForContext:nil]])
1145     return NO;
1146   
1147   if(delegateRespondsTo.didInsertRow)
1148     [delegate adaptorChannel:self didInsertRow:mrow forEntity:entity];
1149
1150   return YES;
1151 }
1152
1153 - (BOOL)updateRow:(NSDictionary *)row
1154   describedByQualifier:(EOSQLQualifier *)qualifier
1155 {
1156   /*
1157     FrontBaseChannel's -updateRow:describedByQualifier: differs from the
1158     EOAdaptorChannel one in that it updates BLOB columns seperatly.
1159   */
1160   FrontBase2Adaptor    *adaptor   = (FrontBase2Adaptor *)[adaptorContext adaptor];
1161   EOEntity            *entity     = [qualifier entity];
1162   EOSQLExpression     *sqlexpr    = nil;
1163   NSMutableDictionary *mrow       = nil;
1164   NSDictionary        *blobRow    = nil;
1165
1166   self->rowsAffected = 0;
1167
1168   mrow = AUTORELEASE([row mutableCopyWithZone:[row zone]]);
1169   
1170   if (!isOpen)
1171     [[ChannelIsNotOpenedException new] raise];
1172
1173   if(row == nil) {
1174     [[[InvalidArgumentException alloc]
1175              initWithFormat:@"row argument for updateRow:describedByQualifier: "
1176        @"must not be the nil object"] raise];
1177   }
1178
1179   if([self isFetchInProgress])
1180     [[AdaptorIsFetchingException exceptionWithAdaptor:self] raise];
1181
1182   if(![adaptorContext transactionNestingLevel])
1183     [[NoTransactionInProgressException exceptionWithAdaptor:self] raise];
1184
1185   if(delegateRespondsTo.willUpdateRow) {
1186     EODelegateResponse response;
1187
1188     response = [delegate adaptorChannel:self
1189                          willUpdateRow:mrow
1190                          describedByQualifier:qualifier];
1191     if(response == EODelegateRejects)
1192       return NO;
1193     else if(response == EODelegateOverrides)
1194       return YES;
1195   }
1196
1197   /* extract BLOB attributes */
1198
1199   blobRow = [self _extractBlobsFromRow:mrow entity:entity];
1200   
1201   /* upload BLOBs */
1202
1203   if (![self _writeBlobs:blobRow ofRow:mrow])
1204     return NO;
1205   
1206   /* update non-BLOB attributes */
1207
1208   if ([mrow count] > 0) {
1209     sqlexpr = [[adaptor expressionClass]
1210                         updateExpressionForRow:mrow
1211                         qualifier:qualifier
1212                         channel:self];
1213     
1214     if(![self evaluateExpression:[sqlexpr expressionValueForContext:nil]])
1215       return NO;
1216
1217     if (self->rowsAffected != 1) {
1218       NSLog(@"%s: rows affected: %i", __PRETTY_FUNCTION__, self->rowsAffected);
1219       return NO;
1220     }
1221   }
1222
1223   /* inform delegate about sucess */
1224   
1225   if(delegateRespondsTo.didUpdateRow) {
1226     [delegate adaptorChannel:self
1227               didUpdateRow:mrow
1228               describedByQualifier:qualifier];
1229   }
1230   return YES;
1231 }
1232
1233 - (BOOL)selectAttributes:(NSArray *)attributes
1234   describedByQualifier:(EOSQLQualifier *)qualifier
1235   fetchOrder:(NSArray *)fetchOrder
1236   lock:(BOOL)lockFlag
1237 {
1238   static Class EOSortOrderingClass = Nil;
1239   NSMutableArray *mattrs;
1240   NSEnumerator *fetchOrders;
1241   id fo;
1242
1243   /* automatically add attributes used in fetchOrderings (required by FB) */
1244   
1245   if (EOSortOrderingClass == Nil)
1246     EOSortOrderingClass = [EOSortOrdering class];
1247   
1248   mattrs = nil;
1249   fetchOrders = [fetchOrder objectEnumerator];
1250   while ((fo = [fetchOrders nextObject])) {
1251     EOAttribute *attr;
1252
1253     attr = nil;
1254     if ([fo isKindOfClass:EOSortOrderingClass]) {
1255       NSString    *attrName;
1256       
1257       attrName = [fo key];
1258       attr = [[qualifier entity] attributeNamed:attrName];
1259     }
1260     else {
1261       /* EOAttributeOrdering */
1262       attr = [fo attribute];
1263     }
1264
1265     if (attr == nil) {
1266       NSLog(@"ERROR(%s): could not resolve attribute of sort ordering %@ !",
1267             __PRETTY_FUNCTION__, fo);
1268       continue;
1269     }
1270     
1271     if (mattrs) {
1272       if (![mattrs containsObject:attr])
1273         [mattrs addObject:attr];
1274     }
1275     else if (![attributes containsObject:attr]) {
1276       mattrs = [[NSMutableArray alloc] initWithArray:attributes];
1277       [mattrs addObject:attr];
1278     }
1279   }
1280   
1281   if (mattrs) {
1282     attributes = [mattrs copy];
1283     RELEASE(mattrs);
1284     AUTORELEASE(attributes);
1285   }
1286
1287   /* continue usual select .. */
1288   
1289   ASSIGN(self->selectedAttributes, attributes);
1290   
1291   return [super selectAttributes:attributes
1292                 describedByQualifier:qualifier
1293                 fetchOrder:fetchOrder
1294                 lock:lockFlag];
1295 }
1296
1297 @end /* FrontBaseChannel(BlobHandling) */
1298
1299 void __link_FBChannel() {
1300   // used to force linking of object file
1301   __link_FBChannel();
1302 }