4 Copyright (C) 2003-2005 SKYRIX Software AG
6 Author: Helge Hess (helge.hess@skyrix.com)
8 This file is part of the SQLite Adaptor Library
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.
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.
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.
29 #include "SQLiteChannel.h"
30 #include "SQLiteAdaptor.h"
31 #include "SQLiteException.h"
32 #include "NSString+SQLite.h"
33 #include "SQLiteValues.h"
34 #include "EOAttribute+SQLite.h"
38 # define MIN(x, y) ((x > y) ? y : x)
41 #define MAX_CHAR_BUF 16384
43 @implementation SQLiteChannel
45 static EONull *null = nil;
48 if (null == NULL) null = [[EONull null] retain];
51 - (id)initWithAdaptorContext:(EOAdaptorContext*)_adaptorContext {
52 if ((self = [super initWithAdaptorContext:_adaptorContext])) {
53 [self setDebugEnabled:[[NSUserDefaults standardUserDefaults]
54 boolForKey:@"SQLiteDebugEnabled"]];
56 self->_attributesForTableName =
57 [[NSMutableDictionary alloc] initWithCapacity:16];
58 self->_primaryKeysNamesForTableName =
59 [[NSMutableDictionary alloc] initWithCapacity:16];
64 - (void)_adaptorWillFinalize:(id)_adaptor {
70 [self->_attributesForTableName release];
71 [self->_primaryKeysNamesForTableName release];
75 /* NSCopying methods */
77 - (id)copyWithZone:(NSZone *)zone {
83 - (void)setDebugEnabled:(BOOL)_flag {
84 self->isDebuggingEnabled = _flag;
86 - (BOOL)isDebugEnabled {
87 return self->isDebuggingEnabled;
90 - (void)receivedMessage:(NSString *)_message {
91 NSLog(@"%@: message %@.", _message);
96 static int openConnectionCount = 0;
99 return (self->_connection != NULL) ? YES : NO;
102 - (int)maxOpenConnectionCount {
103 static int MaxOpenConnectionCount = -1;
105 if (MaxOpenConnectionCount != -1)
106 return MaxOpenConnectionCount;
108 MaxOpenConnectionCount =
109 [[NSUserDefaults standardUserDefaults]
110 integerForKey:@"SQLiteMaxOpenConnectionCount"];
111 if (MaxOpenConnectionCount == 0)
112 MaxOpenConnectionCount = 15;
113 return MaxOpenConnectionCount;
116 - (BOOL)openChannel {
117 const unsigned char *cDBName;
118 SQLiteAdaptor *adaptor;
121 if (self->_connection) {
122 NSLog(@"%s: Connection already open !!!", __PRETTY_FUNCTION__);
126 adaptor = (SQLiteAdaptor *)[adaptorContext adaptor];
128 if (![super openChannel])
131 if (openConnectionCount > [self maxOpenConnectionCount]) {
132 [SQLiteCouldNotOpenChannelException
133 raise:@"NoMoreConnections"
134 format:@"cannot open a additional connection !"];
138 cDBName = [[adaptor databaseName] UTF8String];
140 rc = sqlite3_open(cDBName, (void *)&(self->_connection));
141 if (rc != SQLITE_OK) {
142 // could not login ..
143 // Note: connection *is* set! (might be required to deallocate)
144 NSLog(@"WARNING: could not open SQLite connection to database '%@': %s",
145 [adaptor databaseName], sqlite3_errmsg(self->_connection));
146 sqlite3_close(self->_connection);
150 if (isDebuggingEnabled)
151 NSLog(@"SQLite connection established 0x%08X", self->_connection);
154 NSLog(@"---------- %s: %@ opens channel count[%d]", __PRETTY_FUNCTION__,
155 self, openConnectionCount);
157 openConnectionCount++;
159 #if LIB_FOUNDATION_BOEHM_GC
160 [GarbageCollector registerForFinalizationObserver:self
161 selector:@selector(_adaptorWillFinalize:)
162 object:[[self adaptorContext] adaptor]];
165 if (isDebuggingEnabled) {
166 NSLog(@"SQLite channel 0x%08X opened (connection=0x%08X,%s)",
167 (unsigned)self, self->_connection, cDBName);
172 - (void)primaryCloseChannel {
173 if (self->statement != NULL) {
174 sqlite3_finalize(self->statement);
175 self->statement = NULL;
178 if (self->_connection != NULL) {
179 sqlite3_close(self->_connection);
181 NSLog(@"---------- %s: %@ close channel count[%d]", __PRETTY_FUNCTION__,
182 self, openConnectionCount);
184 openConnectionCount--;
186 if (isDebuggingEnabled) {
188 "SQLite connection dropped 0x%08X (channel=0x%08X)\n",
189 (unsigned)self->_connection, (unsigned)self);
191 self->_connection = NULL;
195 - (void)closeChannel {
196 [super closeChannel];
197 [self primaryCloseChannel];
202 - (NSException *)_makeSQLiteStep {
207 rc = sqlite3_step(self->statement);
209 NSLog(@"STEP: %i (row=%i, done=%i, mis=%i)", rc,
210 SQLITE_ROW, SQLITE_DONE, SQLITE_MISUSE);
213 if (rc == SQLITE_ROW) {
214 self->hasPendingRow = YES;
216 return nil /* no error */;
218 if (rc == SQLITE_DONE) {
219 self->hasPendingRow = NO;
221 return nil /* no error */;
224 if (rc == SQLITE_ERROR)
225 r = [NSString stringWithUTF8String:sqlite3_errmsg(self->_connection)];
226 else if (rc == SQLITE_MISUSE)
227 r = @"The SQLite step function was called in an incorrect way";
228 else if (rc == SQLITE_BUSY)
229 r = @"The SQLite is busy.";
231 r = [NSString stringWithFormat:@"Unexpected SQLite error: %i", rc];
233 if ((em = sqlite3_errmsg(self->_connection)) != NULL)
234 r = [r stringByAppendingFormat:@": %s", em];
236 return [SQLiteException exceptionWithName:@"FetchFailed"
237 reason:r userInfo:nil];
240 - (void)cancelFetch {
241 if (self->statement != NULL) {
242 sqlite3_finalize(self->statement);
243 self->statement = NULL;
246 self->hasPendingRow = NO;
250 - (NSArray *)describeResults {
251 // TODO: make exception-less method
253 NSMutableArray *result = nil;
254 NSMutableDictionary *usedNames = nil;
257 yesObj = [NSNumber numberWithBool:YES];
259 if (![self isFetchInProgress]) {
260 [SQLiteException raise:@"NoFetchInProgress"
261 format:@"No fetch in progress (channel=%@)", self];
264 /* we need to fetch a row to get the info */
266 if (!self->hasPendingRow) {
269 if ((error = [self _makeSQLiteStep]) != nil) {
271 [error raise]; // raise error, TODO: make exception-less method
275 if (!self->hasPendingRow) /* no rows available */
278 fieldCount = sqlite3_column_count(self->statement);
282 result = [[NSMutableArray alloc] initWithCapacity:fieldCount];
283 usedNames = [[NSMutableDictionary alloc] initWithCapacity:fieldCount];
285 for (cnt = 0; cnt < fieldCount; cnt++) {
286 EOAttribute *attribute = nil;
287 NSString *columnName = nil;
288 NSString *attrName = nil;
290 columnName = [NSString stringWithCString:
291 sqlite3_column_name(self->statement, cnt)];
292 attrName = [columnName _sqlite3ModelMakeInstanceVarName];
294 if ([[usedNames objectForKey:attrName] boolValue]) {
297 NSString *newAttrName = nil;
299 for (cnt2 = 2; cnt2 < 100; cnt2++) {
301 sprintf(buf, "%i", cnt2);
304 s = [[NSString alloc] initWithCString:buf];
305 newAttrName = [attrName stringByAppendingString:s];
308 if (![[usedNames objectForKey:newAttrName] boolValue]) {
309 attrName = newAttrName;
314 [usedNames setObject:yesObj forKey:attrName];
316 attribute = [[EOAttribute alloc] init];
317 [attribute setName:attrName];
318 [attribute setColumnName:columnName];
320 switch (sqlite3_column_type(self->statement, cnt)) {
322 [attribute setExternalType:@"INTEGER"];
323 [attribute setValueClassName:@"NSNumber"];
324 [attribute setValueType:@"d"];
327 [attribute setExternalType:@"REAL"];
328 [attribute setValueClassName:@"NSNumber"];
329 [attribute setValueType:@"f"];
332 [attribute setExternalType:@"TEXT"];
333 [attribute setValueClassName:@"NSString"];
336 [attribute setExternalType:@"BLOB"];
337 [attribute setValueClassName:@"NSData"];
340 NSLog(@"WARNING(%s): got SQLite NULL type at column %i, can't derive "
341 @"type information.",
342 __PRETTY_FUNCTION__, cnt);
343 [attribute setExternalType:@"NULL"];
344 [attribute setValueClassName:@"NSNull"];
347 NSLog(@"ERROR(%s): unexpected SQLite type at column %i",
348 __PRETTY_FUNCTION__, cnt);
352 [result addObject:attribute];
359 return [result autorelease];
362 - (BOOL)isColumnNullInCurrentRow:(int)_column {
364 Note: NULL is SQLite is represented as empty strings ..., don't know
365 what to do about that?
366 At least Sybase 10 doesn't support empty strings strings as well
367 and converts them to a single space. So maybe it is reasonable to
368 map empty strings to NSNull?
370 Or is this column-type SQLITE_NULL? If so, thats rather weird,
371 since the type query does not take a row.
376 - (NSMutableDictionary *)primaryFetchAttributes:(NSArray *)_attributes
377 withZone:(NSZone *)_zone
380 Note: we expect that the attributes match the generated SQL. This is
381 because auto-generated SQL can contain SQL table prefixes (like
382 alias.column-name which cannot be detected using the attributes
385 // TODO: add a primaryFetchAttributesX method?
386 NSMutableDictionary *row = nil;
388 unsigned attrCount = [_attributes count];
391 if (self->statement == NULL) {
392 NSLog(@"ERROR: no fetch in progress?");
397 if (!self->hasPendingRow && !self->isDone) {
398 if ((error = [self _makeSQLiteStep]) != nil) {
400 [error raise]; // raise error, TODO: make exception-less method
404 if (self->isDone) { /* step was fine, but we are at the end */
409 self->hasPendingRow = NO; /* consume the row */
413 row = [NSMutableDictionary dictionaryWithCapacity:attrCount];
415 for (cnt = 0; cnt < attrCount; cnt++) {
416 EOAttribute *attribute;
420 attribute = [_attributes objectAtIndex:cnt];
421 attrName = [attribute name];
423 if ([self isColumnNullInCurrentRow:cnt]) {
424 value = [null retain];
429 valueClass = NSClassFromString([attribute valueClassName]);
430 if (valueClass == Nil) {
431 NSLog(@"ERROR(%s): %@: got no value class for column:\n"
432 @" attribute=%@\n type=%@",
433 __PRETTY_FUNCTION__, self,
434 attrName, [attribute externalType]);
439 switch (sqlite3_column_type(self->statement, cnt)) {
441 value = [[valueClass alloc]
442 initWithSQLiteInt:sqlite3_column_int(self->statement, cnt)];
445 value = [[valueClass alloc]
446 initWithSQLiteDouble:
447 sqlite3_column_double(self->statement, cnt)];
450 value = [[valueClass alloc]
452 sqlite3_column_text(self->statement, cnt)];
455 value = [[valueClass alloc]
457 sqlite3_column_blob(self->statement, cnt)
458 length:sqlite3_column_bytes(self->statement, cnt)];
461 value = [null retain];
464 NSLog(@"ERROR(%s): unexpected SQLite type at column %i",
465 __PRETTY_FUNCTION__, cnt);
470 NSLog(@"ERROR(%s): %@: got no value for column:\n"
471 @" attribute=%@\n valueClass=%@\n type=%@",
472 __PRETTY_FUNCTION__, self,
473 attrName, NSStringFromClass(valueClass),
474 [attribute externalType]);
480 [row setObject:value forKey:attrName];
488 /* sending SQL to server */
490 - (NSException *)evaluateExpressionX:(NSString *)_expression {
491 NSMutableString *sql;
495 const char *tails = NULL;
500 if (_expression == nil) {
501 [NSException raise:@"InvalidArgumentException"
502 format:@"parameter for evaluateExpression: "
503 @"must not be null (channel=%@)", self];
506 sql = [[_expression mutableCopy] autorelease];
507 [sql appendString:@";"];
511 if (delegateRespondsTo.willEvaluateExpression) {
512 EODelegateResponse response;
514 response = [delegate adaptorChannel:self willEvaluateExpression:sql];
516 if (response == EODelegateRejects) {
517 return [NSException exceptionWithName:@"EODelegateRejects"
518 reason:@"delegate rejected insert"
521 if (response == EODelegateOverrides)
525 /* check some preconditions */
527 if (![self isOpen]) {
528 return [SQLiteException exceptionWithName:@"ChannelNotOpenException"
529 reason:@"SQLite connection is not open"
532 if (self->statement != NULL) {
533 return [SQLiteException exceptionWithName:@"CommandInProgressException"
534 reason:@"an evaluation is in progress"
539 if ([self isFetchInProgress]) {
540 NSLog(@"WARNING: a fetch is still in progress: %@", self);
544 if (isDebuggingEnabled)
545 NSLog(@"%@ SQL: %@", self, sql);
547 /* reset environment */
549 self->isFetchInProgress = NO;
551 self->hasPendingRow = NO;
553 s = [sql UTF8String];
554 rc = sqlite3_prepare(self->_connection, s, strlen(s),
555 (void *)&(self->statement), &tails);
557 if (rc != SQLITE_OK) {
561 // TODO: improve error
563 r = [NSString stringWithFormat:@"could not parse SQL statement: %s",
564 sqlite3_errmsg(self->_connection)];
565 return [SQLiteException exceptionWithName:@"ExecutionFailed"
566 reason:r userInfo:nil];
569 /* step to first row */
571 if ([sql hasPrefix:@"SELECT"] || [sql hasPrefix:@"select"]) {
572 self->isFetchInProgress = YES;
573 NSAssert(self->statement, @"missing statement");
576 if ((error = [self _makeSQLiteStep]) != nil) {
581 self->isFetchInProgress = self->hasPendingRow;
582 if (!self->isFetchInProgress) {
583 sqlite3_finalize(self->statement);
584 self->statement = NULL;
588 /* only on empty results? */
589 if (delegateRespondsTo.didEvaluateExpression)
590 [delegate adaptorChannel:self didEvaluateExpression:sql];
592 return nil /* everything is OK */;
594 - (BOOL)evaluateExpression:(NSString *)_sql {
598 if ((e = [self evaluateExpressionX:_sql]) == nil)
601 /* for compatibility with non-X methods, translate some errors to a bool */
603 if ([n isEqualToString:@"EOEvaluationError"])
605 if ([n isEqualToString:@"EODelegateRejects"])
614 - (NSString *)description {
617 ms = [NSMutableString stringWithCapacity:64];
618 [ms appendFormat:@"<%@[0x%08X] connection=0x%08X",
619 NSStringFromClass([self class]),
621 (unsigned)self->_connection];
622 [ms appendString:@">"];
626 @end /* SQLiteChannel */
628 @implementation SQLiteChannel(PrimaryKeyGeneration)
630 - (NSDictionary *)primaryKeyForNewRowWithEntity:(EOEntity *)_entity {
632 SQLiteAdaptor *adaptor;
633 NSString *seqName, *seq;
636 pkeys = [_entity primaryKeyAttributeNames];
637 adaptor = (id)[[self adaptorContext] adaptor];
638 seqName = [adaptor primaryKeySequenceName];
642 seq = ([seqName length] > 0)
643 ? [NSString stringWithFormat:@"SELECT NEXTVAL ('%@')", seqName]
644 : [adaptor newKeyExpression];
647 if ([self evaluateExpression:seq]) {
650 NSLog(@"ERROR: new key creation is not implemented in SQLite yet!");
651 if ([self isFetchInProgress]) {
652 NSLog(@"Primary key eval returned results ..");
655 NSLog(@"%s: PKEY GEN NOT IMPLEMENTED!", __PRETTY_FUNCTION__);
659 pkey = [NSDictionary dictionaryWithObject:key
660 forKey:[pkeys objectAtIndex:0]];
672 @end /* SQLiteChannel(PrimaryKeyGeneration) */
674 void __link_SQLiteChannel() {
675 // used to force linking of object file
676 __link_SQLiteChannel();