/* -*-objc-*- */ /** Implementation of SQLClientPostgres 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 Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library 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$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "SQLClient.h" #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; } } - (void) backendExecute: (NSArray*)info { NSString *stmt; CREATE_AUTORELEASE_POOL(arp); stmt = [info objectAtIndex: 0]; if ([stmt length] == 0) { RELEASE (arp); [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 backendConnect] == 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 backendDisconnect]; } if ([self debugging] > 0) { [self debug: @"Error executing statement:\n%@\n%@", stmt, localException]; } RETAIN (localException); RELEASE (arp); AUTORELEASE (localException); [localException raise]; } NS_ENDHANDLER DESTROY(arp); } - (NSMutableArray*) backendQuery: (NSString*)stmt { CREATE_AUTORELEASE_POOL(arp); NSMutableArray *records = [[NSMutableArray alloc] init]; if ([stmt length] == 0) { RELEASE (arp); [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 backendConnect] == 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 = [SQLRecord newWithValues: values keys: keys count: columns]; [records addObject: record]; RELEASE(record); } 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 backendDisconnect]; } if ([self debugging] > 0) { [self debug: @"Error executing statement:\n%@\n%@", stmt, localException]; } RETAIN (localException); RELEASE (arp); AUTORELEASE (localException); [localException raise]; } NS_ENDHANDLER DESTROY(arp); return AUTORELEASE(records); } 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