mirror of
https://github.com/gnustep/libs-sqlclient.git
synced 2025-02-14 15:40:59 +00:00
56fc0088ad
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@36261 72102866-910b-0410-8b05-ffd578937521
370 lines
8.4 KiB
Objective-C
370 lines
8.4 KiB
Objective-C
/* -*-objc-*- */
|
|
|
|
/** Implementation of SQLClientSQLite for GNUStep
|
|
Copyright (C) 2005 Free Software Foundation, Inc.
|
|
|
|
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
|
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 <Foundation/NSAutoreleasePool.h>
|
|
#import <Foundation/NSCalendarDate.h>
|
|
#import <Foundation/NSData.h>
|
|
#import <Foundation/NSDate.h>
|
|
#import <Foundation/NSException.h>
|
|
#import <Foundation/NSLock.h>
|
|
#import <Foundation/NSMapTable.h>
|
|
#import <Foundation/NSNotification.h>
|
|
#import <Foundation/NSNull.h>
|
|
#import <Foundation/NSPathUtilities.h>
|
|
#import <Foundation/NSProcessInfo.h>
|
|
#import <Foundation/NSString.h>
|
|
#import <Foundation/NSUserDefaults.h>
|
|
#import <Foundation/NSValue.h>
|
|
|
|
#include "config.h"
|
|
|
|
#define SQLCLIENT_PRIVATE @public
|
|
|
|
#include "SQLClient.h"
|
|
#include <string.h>
|
|
#include <sqlite3.h>
|
|
|
|
@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
|
|
|