/* -*-objc-*- */ /** Implementation of SQLClientSQLite for GNUStep Copyright (C) 2005 Free Software Foundation, Inc. Written by: Richard Frith-Macdonald Date: Nov 2005 This file is part of the SQLClient Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. $Date$ $Revision$ */ #import #import #import #import #import #import #import #import #import #import #import #import #import #import #include "config.h" #define SQLCLIENT_PRIVATE @public #include "SQLClient.h" #include #include @interface SQLClientSQLite : SQLClient @end @implementation SQLClientSQLite /* use [self database] as path to database file */ - (BOOL) backendConnect { if (connected == NO) { if ([self database] != nil) { NSString *dbase = [self database]; sqlite3 *sql; int result; [[self class] purgeConnections: nil]; if ([self debugging] > 0) { [self debug: @"Connect to '%@' as %@", [self database], [self name]]; } result = sqlite3_open([dbase fileSystemRepresentation], &sql); if (result != 0) { [self debug: @"Error connecting to '%@' (%@) - %s", [self name], [self database], sqlite3_errmsg(sql)]; sqlite3_close(sql); extra = 0; } else { connected = YES; extra = sql; if ([self debugging] > 0) { [self debug: @"Connected to '%@'", [self name]]; } } } else { [self debug: @"Connect to '%@' with no database configured", [self name]]; } } return connected; } - (void) backendDisconnect { if (connected == YES) { NS_DURING { if ([self isInTransaction] == YES) { [self rollback]; } if ([self debugging] > 0) { [self debug: @"Disconnecting client %@", [self clientName]]; } sqlite3_close((sqlite3 *)extra); extra = 0; if ([self debugging] > 0) { [self debug: @"Disconnected client %@", [self clientName]]; } } NS_HANDLER { extra = 0; [self debug: @"Error disconnecting from database (%@): %@", [self clientName], localException]; } NS_ENDHANDLER connected = NO; } } - (NSInteger) backendExecute: (NSArray*)info { NSString *stmt; NSAutoreleasePool *arp = [NSAutoreleasePool new]; stmt = [info objectAtIndex: 0]; if ([stmt length] == 0) { [arp release]; [NSException raise: NSInternalInconsistencyException format: @"Statement produced null string"]; } NS_DURING { const char *statement; unsigned length; int result; char *err; /* * Ensure we have a working connection. */ if ([self connect] == NO) { [NSException raise: SQLException format: @"Unable to connect to '%@' to execute statement %@", [self name], stmt]; } statement = (char*)[stmt UTF8String]; length = strlen(statement); statement = [self insertBLOBs: info intoStatement: statement length: length withMarker: "'?'''?'" length: 7 giving: &length]; result = sqlite3_exec((sqlite3 *)extra, statement, 0, 0, &err); if (result != SQLITE_OK) { [NSException raise: SQLException format: @"%s", err]; } } NS_HANDLER { NSString *n = [localException name]; if ([n isEqual: SQLConnectionException] == YES) { [self disconnect]; } if ([self debugging] > 0) { [self debug: @"Error executing statement:\n%@\n%@", stmt, localException]; } [localException retain]; [arp release]; [localException autorelease]; [localException raise]; } NS_ENDHANDLER [arp release]; return -1; } - (NSMutableArray*) backendQuery: (NSString*)stmt recordType: (id)rtype listType: (id)ltype { NSAutoreleasePool *arp = [NSAutoreleasePool new]; NSMutableArray *records = [[ltype alloc] initWithCapacity: 2]; if ([stmt length] == 0) { [arp release]; [NSException raise: NSInternalInconsistencyException format: @"Statement produced null string"]; } NS_DURING { char *statement; int result; sqlite3_stmt *prepared; const char *stmtEnd; /* * Ensure we have a working connection. */ if ([self connect] == NO) { [NSException raise: SQLException format: @"Unable to connect to '%@' to run query %@", [self name], stmt]; } statement = (char*)[stmt UTF8String]; result = sqlite3_prepare((sqlite3 *)extra, statement, strlen(statement), &prepared, &stmtEnd); if (result != SQLITE_OK) { [NSException raise: SQLException format: @"Unable to prepare '%@'", stmt]; } if ((result = sqlite3_step(prepared)) == SQLITE_ROW) { int columns = sqlite3_column_count(prepared); NSString *keys[columns]; int i; for (i = 0; i < columns; i++) { keys[i] = [NSString stringWithUTF8String: sqlite3_column_name(prepared, i)]; } do { id values[columns]; SQLRecord *record; for (i = 0; i < columns; i++) { int type = sqlite3_column_type(prepared, i); switch (type) { case SQLITE_INTEGER: values[i] = [NSNumber numberWithInt: sqlite3_column_int(prepared, i)]; break; case SQLITE_FLOAT: values[i] = [NSNumber numberWithDouble: sqlite3_column_double(prepared, i)]; break; case SQLITE_TEXT: values[i] = [NSString stringWithUTF8String: (char *)sqlite3_column_text(prepared, i)]; break; case SQLITE_BLOB: values[i] = [NSData dataWithBytes: sqlite3_column_blob(prepared, i) length: sqlite3_column_bytes(prepared, i)]; break; case SQLITE_NULL: values[i] = nil; break; } } record = [rtype newWithValues: values keys: keys count: columns]; [records addObject: record]; [record release]; } while ((result = sqlite3_step(prepared)) == SQLITE_ROW); } if (result != SQLITE_DONE) { [NSException raise: SQLException format: @"%s", sqlite3_errmsg((sqlite3 *)extra)]; } sqlite3_finalize(prepared); } NS_HANDLER { NSString *n = [localException name]; if ([n isEqual: SQLConnectionException] == YES) { [self disconnect]; } if ([self debugging] > 0) { [self debug: @"Error executing statement:\n%@\n%@", stmt, localException]; } [localException retain]; [arp release]; [localException autorelease]; [localException raise]; } NS_ENDHANDLER [arp release]; return [records autorelease]; } static char hex[16] = "0123456789ABCDEF"; - (unsigned) copyEscapedBLOB: (NSData*)blob into: (void*)buf { const unsigned char *bytes = [blob bytes]; unsigned char *ptr = buf; unsigned length = [blob length]; unsigned i; *ptr++ = 'X'; *ptr++ = '\''; for (i = 0; i < length; i++) { unsigned char c = bytes[i]; *ptr++ = hex[c / 16]; *ptr++ = hex[c % 16];; } *ptr++ = '\''; return ((void*)ptr - buf); } - (unsigned) lengthOfEscapedBLOB: (NSData*)blob { /* * A blob is X'xx' where xx is hexadecimal encoded binary data ... * two hex digits per byte. */ return 3 + [blob length] * 2; } - (NSString*) quote: (id)obj { if ([obj isKindOfClass: [NSDate class]] == YES) { obj = [NSNumber numberWithDouble: [obj timeIntervalSinceReferenceDate]]; } return [super quote: obj]; } @end