mirror of
https://github.com/gnustep/libs-sqlclient.git
synced 2025-02-14 15:40:59 +00:00
0c1eebfc6c
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@37063 72102866-910b-0410-8b05-ffd578937521
1147 lines
25 KiB
Objective-C
1147 lines
25 KiB
Objective-C
/* -*-objc-*- */
|
|
|
|
/** Implementation of SQLClientECPG for GNUStep
|
|
Copyright (C) 2004 Free Software Foundation, Inc.
|
|
|
|
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
|
Date: April 2004
|
|
|
|
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$
|
|
*/
|
|
|
|
#include <Foundation/NSString.h>
|
|
#include <Foundation/NSData.h>
|
|
#include <Foundation/NSDate.h>
|
|
#include <Foundation/NSCalendarDate.h>
|
|
#include <Foundation/NSException.h>
|
|
#include <Foundation/NSProcessInfo.h>
|
|
#include <Foundation/NSNotification.h>
|
|
#include <Foundation/NSUserDefaults.h>
|
|
#include <Foundation/NSMapTable.h>
|
|
#include <Foundation/NSLock.h>
|
|
#include <Foundation/NSNull.h>
|
|
#include <Foundation/NSValue.h>
|
|
#include <Foundation/NSAutoreleasePool.h>
|
|
|
|
#include "config.h"
|
|
|
|
#define SQLCLIENT_PRIVATE @public
|
|
|
|
#include "SQLClient.h"
|
|
|
|
@interface SQLClientECPG : SQLClient
|
|
@end
|
|
|
|
@interface SQLClientECPG(Embedded)
|
|
- (const char *) blobFromData: (NSData*)data;
|
|
- (NSData *) dataFromBlob: (const char *)blob;
|
|
- (BOOL) dbFromDate: (NSDate*)d toBuffer: (char*)b length: (int)l;
|
|
- (BOOL) dbFromString: (NSString*)s toBuffer: (char*)b length: (int)l;
|
|
- (NSDate*) dbToDateFromBuffer: (char*)b length: (int)l;
|
|
- (NSString*) dbToStringFromBuffer: (char*)b length: (int)l;
|
|
@end
|
|
|
|
/* This looks like a Postgres specific issue/feature/bug - for some
|
|
* reason, ':' inside a string often causes the string to be
|
|
* horribly mutilated before being handed to the database
|
|
* ... probably postgres is trying to replace variables or something
|
|
* - avoid all these problems by replacing ':' with its octal code
|
|
* \\072.
|
|
*/
|
|
static NSString *
|
|
hackForPrepare(NSString *s)
|
|
{
|
|
NSRange r;
|
|
|
|
r = [s rangeOfString: @":"];
|
|
if (r.length > 0)
|
|
{
|
|
s = [s stringByReplacingString: @":" withString: @"\\072"];
|
|
}
|
|
return s;
|
|
}
|
|
|
|
|
|
EXEC SQL INCLUDE sql3types;
|
|
EXEC SQL INCLUDE sqlca;
|
|
EXEC SQL WHENEVER SQLERROR CALL SQLErrorHandler();
|
|
|
|
/**
|
|
* Return YES of the last SQL error indicated we are out of data,
|
|
* NO otherwise.
|
|
*/
|
|
BOOL SQLOutOfData()
|
|
{
|
|
if (sqlca.sqlcode == 100)
|
|
{
|
|
return YES;
|
|
}
|
|
else
|
|
{
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* This error handler is called for most errors ... so we can get it to
|
|
* raise an exception for us.
|
|
*/
|
|
void SQLErrorHandler()
|
|
{
|
|
int code = sqlca.sqlcode;
|
|
const char *ptr = sqlca.sqlerrm.sqlerrmc;
|
|
const char *e0 = "'no connection to the server'";
|
|
const char *e1 = "Error in transaction processing";
|
|
|
|
sqlca.sqlcode = 0; // Reset error code
|
|
NSLog (@"Raising an exception, %d, %s", code, sqlca.sqlerrm.sqlerrmc);
|
|
|
|
if (strncmp(ptr, e0, strlen(e0)) == 0
|
|
|| strncmp(ptr, e1, strlen(e1)) == 0)
|
|
{
|
|
[NSException raise: SQLConnectionException
|
|
format: @"SQL Error: SQLCODE=(%d): %s", code, ptr];
|
|
}
|
|
else
|
|
{
|
|
[NSException raise: SQLException
|
|
format: @"SQL Error: SQLCODE=(%d): %s", code, ptr];
|
|
}
|
|
}
|
|
|
|
@implementation SQLClientECPG
|
|
|
|
static NSDate *future = nil;
|
|
|
|
+ (void) initialize
|
|
{
|
|
if (future == nil)
|
|
{
|
|
future = [NSCalendarDate dateWithString: @"9999-01-01 00:00:00 +0000"
|
|
calendarFormat: @"%Y-%m-%d %H:%M:%S %z"
|
|
locale: nil];
|
|
[future retain];
|
|
}
|
|
}
|
|
|
|
- (BOOL) backendConnect
|
|
{
|
|
if (connected == NO)
|
|
{
|
|
if ([self database] != nil
|
|
&& [self user] != nil
|
|
&& [self password] != nil)
|
|
{
|
|
Class c = NSClassFromString(@"CmdClient");
|
|
|
|
[[self class] purgeConnections: nil];
|
|
|
|
NS_DURING
|
|
{
|
|
EXEC SQL BEGIN DECLARE SECTION;
|
|
const char *database_c;
|
|
const char *user_c;
|
|
const char *password_c;
|
|
const char *client_c;
|
|
EXEC SQL END DECLARE SECTION;
|
|
|
|
database_c = [[self database] UTF8String];
|
|
user_c = [[self user] UTF8String];
|
|
password_c = [[self password] UTF8String];
|
|
client_c = [[self clientName] UTF8String];
|
|
|
|
if (c != 0)
|
|
{
|
|
if ([self debugging] > 0)
|
|
{
|
|
[self debug:
|
|
@"Connect to '%@' database %s user %s as %s",
|
|
[self name], database_c, user_c, client_c];
|
|
}
|
|
}
|
|
EXEC SQL CONNECT TO :database_c
|
|
AS :client_c
|
|
USER :user_c
|
|
USING :password_c;
|
|
|
|
connected = YES;
|
|
if (c != 0)
|
|
{
|
|
if ([self debugging] > 0)
|
|
{
|
|
[self debug: @"Connected to '%@' (%s)",
|
|
[self name], client_c];
|
|
}
|
|
}
|
|
EXEC SQL AT :client_c SET AUTOCOMMIT TO ON;
|
|
// For backwards compat, make this the default
|
|
EXEC SQL SET CONNECTION TO :client_c;
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
[self debug: @"Error connecting to '%@' database: %@",
|
|
[self name], localException];
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
else
|
|
{
|
|
[self debug:
|
|
@"Connect to '%@' with no user/password/database configured",
|
|
[self name]];
|
|
}
|
|
}
|
|
return connected;
|
|
}
|
|
|
|
- (void) backendDisconnect
|
|
{
|
|
if (connected == YES)
|
|
{
|
|
NS_DURING
|
|
{
|
|
EXEC SQL BEGIN DECLARE SECTION;
|
|
const char *client_c;
|
|
EXEC SQL END DECLARE SECTION;
|
|
|
|
if ([self isInTransaction] == YES)
|
|
{
|
|
[self rollback];
|
|
}
|
|
|
|
client_c = [[self clientName] UTF8String];
|
|
|
|
if ([self debugging] > 0)
|
|
{
|
|
[self debug: @"Disconnecting client %@", [self clientName]];
|
|
}
|
|
EXEC SQL DISCONNECT :client_c;
|
|
if ([self debugging] > 0)
|
|
{
|
|
[self debug: @"Disconnected client %@", [self clientName]];
|
|
}
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
[self debug: @"Error disconnecting from database (%@): %@",
|
|
[self clientName], localException];
|
|
}
|
|
NS_ENDHANDLER
|
|
connected = NO;
|
|
}
|
|
}
|
|
|
|
- (NSInteger) backendExecute: (NSArray*)info
|
|
{
|
|
EXEC SQL BEGIN DECLARE SECTION;
|
|
char *statement;
|
|
char *handle;
|
|
EXEC SQL END DECLARE SECTION;
|
|
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
|
unsigned int length;
|
|
NSString *stmt = [info objectAtIndex: 0];
|
|
|
|
length = [stmt length];
|
|
if (length == 0)
|
|
{
|
|
[arp release];
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"Statement produced null string"];
|
|
}
|
|
|
|
statement = (char*)[hackForPrepare(stmt) UTF8String];
|
|
length = strlen(statement);
|
|
statement = (char*)[self insertBLOBs: info
|
|
intoStatement: statement
|
|
length: length
|
|
withMarker: "'''"
|
|
length: 3
|
|
giving: &length];
|
|
handle = (char*)[[self clientName] UTF8String];
|
|
|
|
/*
|
|
* Ensure we have a working connection.
|
|
*/
|
|
if ([self connect] == NO)
|
|
{
|
|
[arp release];
|
|
[NSException raise: SQLException
|
|
format: @"Unable to connect to '%@' to execute statement %@",
|
|
[self name], stmt];
|
|
}
|
|
|
|
NS_DURING
|
|
{
|
|
EXEC SQL PREPARE command from :statement;
|
|
EXEC SQL AT :handle EXECUTE command;
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSString *n = [localException name];
|
|
NSString *msg = [localException reason];
|
|
|
|
if ([n isEqual: SQLConnectionException] == YES)
|
|
{
|
|
[self disconnect];
|
|
}
|
|
/*
|
|
* remove line number information from database exception message
|
|
* since it's meaningless to the developer as it's the line number
|
|
* in this file rather than the code which is calling us.
|
|
*/
|
|
if ([n isEqual: SQLException] == YES
|
|
|| [n isEqual: SQLConnectionException] == YES)
|
|
{
|
|
NSRange r;
|
|
|
|
r = [msg rangeOfString: @" in line " options: NSBackwardsSearch];
|
|
if (r.length > 0)
|
|
{
|
|
msg = [msg substringToIndex: r.location];
|
|
localException = [NSException exceptionWithName: n
|
|
reason: msg
|
|
userInfo: nil];
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
static unsigned int trim(char *str)
|
|
{
|
|
char *start = str;
|
|
|
|
while (isspace(*str))
|
|
{
|
|
str++;
|
|
}
|
|
if (str != start)
|
|
{
|
|
strcpy(start, str);
|
|
}
|
|
str = start;
|
|
while (*str != '\0')
|
|
{
|
|
str++;
|
|
}
|
|
while (str > start && isspace(str[-1]))
|
|
{
|
|
*--str = '\0';
|
|
}
|
|
return (str - start);
|
|
}
|
|
|
|
- (NSMutableArray*) backendQuery: (NSString*)stmt
|
|
recordType: (id)rtype
|
|
listType: (id)ltype
|
|
{
|
|
EXEC SQL BEGIN DECLARE SECTION;
|
|
bool aBool;
|
|
int anInt;
|
|
int count;
|
|
int index;
|
|
int indicator;
|
|
int type;
|
|
int length;
|
|
int octetLength;
|
|
int precision;
|
|
int scale;
|
|
int returnedOctetLength;
|
|
int dtiCode;
|
|
char fieldName[120];
|
|
char *aString;
|
|
float aFloat;
|
|
double aDouble;
|
|
char *query;
|
|
char *handle;
|
|
EXEC SQL END DECLARE SECTION;
|
|
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
|
NSMutableArray *records;
|
|
BOOL isOpen = NO;
|
|
BOOL localTransaction = NO;
|
|
|
|
length = [stmt length];
|
|
if (length == 0)
|
|
{
|
|
[arp release];
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"Statement produced null string"];
|
|
}
|
|
|
|
query = (char*)[hackForPrepare(stmt) UTF8String];
|
|
handle = (char*)[[self clientName] UTF8String];
|
|
records = [[ltype alloc] initWithCapacity: 32];
|
|
|
|
/*
|
|
* Ensure we have a working connection.
|
|
*/
|
|
if ([self connect] == NO)
|
|
{
|
|
[arp release];
|
|
[NSException raise: SQLException
|
|
format: @"Unable to connect to '%@' to run query %@",
|
|
[self name], stmt];
|
|
}
|
|
|
|
NS_DURING
|
|
{
|
|
EXEC SQL ALLOCATE DESCRIPTOR myDesc;
|
|
EXEC SQL PREPARE myQuery from :query;
|
|
if ([self isInTransaction] == NO)
|
|
{
|
|
EXEC SQL AT :handle BEGIN;
|
|
localTransaction = YES;
|
|
}
|
|
EXEC SQL AT :handle DECLARE myCursor CURSOR FOR myQuery;
|
|
EXEC SQL AT :handle OPEN myCursor;
|
|
isOpen = YES;
|
|
|
|
while (1)
|
|
{
|
|
EXEC SQL AT :handle FETCH IN myCursor INTO SQL DESCRIPTOR myDesc;
|
|
|
|
if (sqlca.sqlcode)
|
|
{
|
|
break;
|
|
}
|
|
EXEC SQL GET DESCRIPTOR myDesc :count = COUNT;
|
|
|
|
if (count > 0)
|
|
{
|
|
SQLRecord *record;
|
|
id keys[count];
|
|
id values[count];
|
|
|
|
for (index = 1; index <= count; ++index)
|
|
{
|
|
id v;
|
|
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:indicator = INDICATOR,
|
|
:length = LENGTH,
|
|
:fieldName = NAME,
|
|
:octetLength = OCTET_LENGTH,
|
|
:precision = PRECISION,
|
|
:returnedOctetLength = RETURNED_OCTET_LENGTH,
|
|
:scale = SCALE,
|
|
:type = TYPE;
|
|
|
|
// printf("%s type:%d scale:%d\n", fieldName, type, scale);
|
|
|
|
if (indicator == -1)
|
|
{
|
|
v = [NSNull null];
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* HACK ... for some reason date/time data seems to
|
|
* get a negative type returned, so we check any
|
|
* negative time to see if it is really date/time
|
|
* and bodg the type code to fit.
|
|
*/
|
|
if (type < 0)
|
|
{
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:dtiCode = DATETIME_INTERVAL_CODE;
|
|
if (dtiCode != SQL3_DDT_ILLEGAL)
|
|
{
|
|
type = SQL3_DATE_TIME_TIMESTAMP;
|
|
}
|
|
}
|
|
|
|
aString = 0;
|
|
switch (type)
|
|
{
|
|
case SQL3_BOOLEAN:
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:aBool = DATA;
|
|
if (aBool == 1)
|
|
{
|
|
v = [NSNumber numberWithBool: YES];
|
|
}
|
|
else if (aBool == 0)
|
|
{
|
|
v = [NSNumber numberWithBool: NO];
|
|
}
|
|
else
|
|
{
|
|
[NSException raise: NSGenericException
|
|
format: @"Bad bool for '%s' - '%d'",
|
|
fieldName, aBool];
|
|
}
|
|
break;
|
|
|
|
case SQL3_NUMERIC:
|
|
case SQL3_DECIMAL:
|
|
if (scale == 0)
|
|
{
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:anInt = DATA;
|
|
v = [NSNumber numberWithInt: anInt];
|
|
}
|
|
else
|
|
{
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:aFloat = DATA;
|
|
v = [NSNumber numberWithFloat: aFloat];
|
|
}
|
|
break;
|
|
|
|
case SQL3_INTEGER:
|
|
case SQL3_SMALLINT:
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:anInt = DATA;
|
|
v = [NSNumber numberWithInt: anInt];
|
|
break;
|
|
|
|
case SQL3_FLOAT:
|
|
case SQL3_REAL:
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:aFloat = DATA;
|
|
v = [NSNumber numberWithFloat: aFloat];
|
|
break;
|
|
|
|
case SQL3_DOUBLE_PRECISION:
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:aDouble = DATA;
|
|
v = [NSNumber numberWithDouble: aDouble];
|
|
break;
|
|
|
|
case SQL3_DATE_TIME_TIMESTAMP:
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:dtiCode = DATETIME_INTERVAL_CODE,
|
|
:aString = DATA;
|
|
v = [self dbToDateFromBuffer: aString
|
|
length: trim(aString)];
|
|
free(aString);
|
|
break;
|
|
|
|
case SQL3_INTERVAL:
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:aString = DATA;
|
|
trim(aString);
|
|
v = [NSString stringWithUTF8String: aString];
|
|
free(aString);
|
|
break;
|
|
|
|
case SQL3_CHARACTER:
|
|
case SQL3_CHARACTER_VARYING:
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:aString = DATA;
|
|
if (_shouldTrim)
|
|
{
|
|
trim(aString);
|
|
}
|
|
v = [NSString stringWithUTF8String: aString];
|
|
free(aString);
|
|
break;
|
|
|
|
case -17:
|
|
/*
|
|
* HACK ... BYTEA type determined by experiment.
|
|
* who knows how/why this might change.
|
|
*/
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:aString = DATA;
|
|
v = [self dataFromBlob: aString];
|
|
free(aString);
|
|
break;
|
|
|
|
case -20:
|
|
/*
|
|
* HACK ... by experiment this seems to be an
|
|
* integer returned by a function.
|
|
*/
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:anInt = DATA;
|
|
v = [NSNumber numberWithInt: anInt];
|
|
break;
|
|
|
|
default:
|
|
EXEC SQL GET DESCRIPTOR myDesc VALUE :index
|
|
:aString = DATA;
|
|
if (_shouldTrim)
|
|
{
|
|
trim(aString);
|
|
}
|
|
v = [NSString stringWithUTF8String: aString];
|
|
free(aString);
|
|
if ([self debugging] > 0)
|
|
{
|
|
[self debug:
|
|
@"Unknown data type (%d) for '%s' ... '%@'",
|
|
type, fieldName, v];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
values[index-1] = v;
|
|
keys[index-1] = [NSString stringWithUTF8String:
|
|
fieldName];
|
|
}
|
|
record = [rtype newWithValues: values
|
|
keys: keys
|
|
count: count];
|
|
[records addObject: record];
|
|
[record release];
|
|
}
|
|
}
|
|
|
|
isOpen = NO;
|
|
EXEC SQL AT :handle CLOSE myCursor;
|
|
if (localTransaction == YES)
|
|
{
|
|
EXEC SQL AT :handle COMMIT;
|
|
localTransaction = NO;
|
|
}
|
|
EXEC SQL DEALLOCATE DESCRIPTOR myDesc;
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSString *n = [localException name];
|
|
NSString *msg = [localException reason];
|
|
|
|
[records release];
|
|
records = nil;
|
|
|
|
NS_DURING
|
|
{
|
|
if (isOpen == YES)
|
|
{
|
|
EXEC SQL AT :handle CLOSE myCursor;
|
|
}
|
|
if (localTransaction == YES)
|
|
{
|
|
EXEC SQL AT :handle ROLLBACK;
|
|
}
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSString *e = [localException name];
|
|
|
|
if ([e isEqual: SQLConnectionException] == YES)
|
|
{
|
|
[self disconnect];
|
|
}
|
|
}
|
|
NS_ENDHANDLER
|
|
|
|
if ([n isEqual: SQLConnectionException] == YES)
|
|
{
|
|
[self disconnect];
|
|
}
|
|
|
|
/*
|
|
* remove line number information from database exception message
|
|
* since it's meaningless to the developer as it's the line number
|
|
* in this file rather than the code which is calling us.
|
|
*/
|
|
if ([n isEqual: SQLException] == YES
|
|
|| [n isEqual: SQLConnectionException] == YES)
|
|
{
|
|
NSRange r;
|
|
|
|
r = [msg rangeOfString: @" in line " options: NSBackwardsSearch];
|
|
if (r.length > 0)
|
|
{
|
|
msg = [msg substringToIndex: r.location];
|
|
localException = [NSException exceptionWithName: n
|
|
reason: msg
|
|
userInfo: nil];
|
|
}
|
|
}
|
|
[localException retain];
|
|
[arp release];
|
|
[localException autorelease];
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER
|
|
[arp release];
|
|
|
|
return [records autorelease];
|
|
}
|
|
|
|
- (unsigned) copyEscapedBLOB: (NSData*)blob into: (void*)buf
|
|
{
|
|
const unsigned char *src = [blob bytes];
|
|
unsigned sLen = [blob length];
|
|
unsigned char *ptr = (unsigned char*)buf;
|
|
unsigned length = 0;
|
|
unsigned i;
|
|
|
|
ptr[length++] = '\'';
|
|
for (i = 0; i < sLen; i++)
|
|
{
|
|
unsigned char c = src[i];
|
|
|
|
if (c < 32 || c > 126 || c == ':')
|
|
{
|
|
ptr[length] = '\\';
|
|
ptr[length+1] = '\\';
|
|
ptr[length + 4] = (c & 7) + '0';
|
|
c >>= 3;
|
|
ptr[length + 3] = (c & 7) + '0';
|
|
c >>= 3;
|
|
ptr[length + 2] = (c & 7) + '0';
|
|
length += 5;
|
|
}
|
|
else if (c == '\\')
|
|
{
|
|
ptr[length++] = '\\';
|
|
ptr[length++] = '\\';
|
|
ptr[length++] = '\\';
|
|
ptr[length++] = '\\';
|
|
}
|
|
else if (c == '\'')
|
|
{
|
|
ptr[length++] = '\\';
|
|
ptr[length++] = '\'';
|
|
}
|
|
else
|
|
{
|
|
ptr[length++] = c;
|
|
}
|
|
}
|
|
ptr[length++] = '\'';
|
|
return length;
|
|
}
|
|
|
|
- (unsigned) lengthOfEscapedBLOB: (NSData*)blob
|
|
{
|
|
unsigned int sLen = [blob length];
|
|
unsigned char *src = (unsigned char*)[blob bytes];
|
|
unsigned int length = 2;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < sLen; i++)
|
|
{
|
|
unsigned char c = src[i];
|
|
|
|
if (c < 32 || c > 126 || c == ':')
|
|
{
|
|
length += 5;
|
|
}
|
|
else if (c == '\\')
|
|
{
|
|
length += 4;
|
|
}
|
|
else if (c == '\'')
|
|
{
|
|
length += 2;
|
|
}
|
|
else
|
|
{
|
|
length += 1;
|
|
}
|
|
}
|
|
return length;
|
|
}
|
|
|
|
- (const char *) blobFromData: (NSData*)data
|
|
{
|
|
NSMutableData *md;
|
|
unsigned sLen = [data length];
|
|
unsigned char *src = (unsigned char*)[data bytes];
|
|
unsigned dLen = 0;
|
|
unsigned char *dst;
|
|
unsigned i;
|
|
|
|
for (i = 0; i < sLen; i++)
|
|
{
|
|
unsigned char c = src[i];
|
|
|
|
if (c < 32 || c > 126)
|
|
{
|
|
dLen += 4;
|
|
}
|
|
else if (c == 92)
|
|
{
|
|
dLen += 2;
|
|
}
|
|
else
|
|
{
|
|
dLen += 1;
|
|
}
|
|
}
|
|
md = [NSMutableData dataWithLength: dLen + 1];
|
|
dst = (unsigned char*)[md mutableBytes];
|
|
|
|
dLen = 0;
|
|
for (i = 0; i < sLen; i++)
|
|
{
|
|
unsigned char c = src[i];
|
|
|
|
if (c < 32 || c > 126)
|
|
{
|
|
dst[dLen] = '\\';
|
|
dst[dLen + 3] = (c & 7) + '0';
|
|
c >>= 3;
|
|
dst[dLen + 2] = (c & 7) + '0';
|
|
c >>= 3;
|
|
dst[dLen + 1] = (c & 7) + '0';
|
|
dLen += 4;
|
|
}
|
|
else if (c == 92)
|
|
{
|
|
dst[dLen++] = '\\';
|
|
dst[dLen++] = '\\';
|
|
}
|
|
else
|
|
{
|
|
dst[dLen++] = c;
|
|
}
|
|
}
|
|
dst[dLen] = '\0';
|
|
return (const char*)dst; // Owned by autoreleased NSMutableData
|
|
}
|
|
|
|
- (NSData *) dataFromBlob: (const char *)blob
|
|
{
|
|
NSMutableData *md;
|
|
unsigned sLen = strlen(blob == 0 ? "" : blob);
|
|
unsigned dLen = 0;
|
|
unsigned char *dst;
|
|
unsigned i;
|
|
|
|
for (i = 0; i < sLen; i++)
|
|
{
|
|
unsigned c = blob[i];
|
|
|
|
dLen++;
|
|
if (c == '\\')
|
|
{
|
|
c = blob[++i];
|
|
if (c != '\\')
|
|
{
|
|
i += 2; // Skip 2 digits octal
|
|
}
|
|
}
|
|
}
|
|
md = [NSMutableData dataWithLength: dLen];
|
|
dst = (unsigned char*)[md mutableBytes];
|
|
|
|
dLen = 0;
|
|
for (i = 0; i < sLen; i++)
|
|
{
|
|
unsigned c = blob[i];
|
|
|
|
if (c == '\\')
|
|
{
|
|
c = blob[++i];
|
|
if (c != '\\')
|
|
{
|
|
c = c - '0';
|
|
c <<= 3;
|
|
c += blob[++i] - '0';
|
|
c <<= 3;
|
|
c += blob[++i] - '0';
|
|
}
|
|
}
|
|
dst[dLen++] = c;
|
|
}
|
|
return md;
|
|
}
|
|
|
|
- (BOOL) dbFromDate: (NSDate*)d toBuffer: (char*)b length: (int)l
|
|
{
|
|
NSString *s;
|
|
|
|
/*
|
|
* Ensure we have a four digit year.
|
|
*/
|
|
if ([d timeIntervalSinceDate: future] > 0)
|
|
{
|
|
d = future;
|
|
}
|
|
s = [d descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z"
|
|
timeZone: nil
|
|
locale: nil];
|
|
return [self dbFromString: s toBuffer: b length: l];
|
|
}
|
|
|
|
- (BOOL) dbFromString: (NSString*)s toBuffer: (char*)b length: (int)l
|
|
{
|
|
NSData *d;
|
|
BOOL ok = YES;
|
|
unsigned size = l;
|
|
|
|
if (l <= 0)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"-%@: length too small (%d)",
|
|
NSStringFromSelector(_cmd), l];
|
|
}
|
|
if (b == 0)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"-%@: buffer is null",
|
|
NSStringFromSelector(_cmd)];
|
|
}
|
|
if (s == nil)
|
|
{
|
|
s = @"";
|
|
}
|
|
d = [s dataUsingEncoding: NSUTF8StringEncoding];
|
|
if (l < (int)[d length])
|
|
{
|
|
/*
|
|
* As the data is UTF8, we need to avoid truncating in the
|
|
* middle of a multibyte character, so we shorten the
|
|
* original string and reconvert to UTF8 until we find a
|
|
* string that fits.
|
|
*/
|
|
if ((int)[s length] > l)
|
|
{
|
|
s = [s substringToIndex: l];
|
|
d = [s dataUsingEncoding: NSUTF8StringEncoding];
|
|
}
|
|
while ((int)[d length] > l)
|
|
{
|
|
s = [s substringToIndex: [s length] - 1];
|
|
d = [s dataUsingEncoding: NSUTF8StringEncoding];
|
|
}
|
|
ok = NO;
|
|
}
|
|
size = [d length];
|
|
memcpy(b, (const char*)[d bytes], size);
|
|
/*
|
|
* Pad with nuls and ensure there is a nul terminator.
|
|
*/
|
|
while ((int)size <= l)
|
|
{
|
|
b[size++] = '\0';
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
- (NSDate*) dbToDateFromBuffer: (char*)b length: (int)l
|
|
{
|
|
char buf[l+32]; /* Allow space to expand buffer. */
|
|
NSCalendarDate *d;
|
|
BOOL milliseconds = NO;
|
|
BOOL timezone = NO;
|
|
NSString *s;
|
|
int i;
|
|
int e;
|
|
|
|
memcpy(buf, b, l);
|
|
b = buf;
|
|
/*
|
|
* Find end of string.
|
|
*/
|
|
for (i = 0; i < l; i++)
|
|
{
|
|
if (b[i] == '\0')
|
|
{
|
|
l = i;
|
|
break;
|
|
}
|
|
}
|
|
while (l > 0 && isspace(b[l-1]))
|
|
{
|
|
l--;
|
|
}
|
|
b[l] = '\0';
|
|
|
|
if (l == 10)
|
|
{
|
|
s = [NSString stringWithUTF8String: b];
|
|
return [NSCalendarDate dateWithString: s
|
|
calendarFormat: @"%Y-%m-%d"
|
|
locale: nil];
|
|
}
|
|
|
|
i = l;
|
|
|
|
/* Convert +/-HH:SS timezone to +/-HHSS
|
|
*/
|
|
if (i > 5 && b[i-3] == ':' && (b[i-6] == '+' || b[i-6] == '-'))
|
|
{
|
|
b[i-3] = b[i-2];
|
|
b[i-2] = b[i-1];
|
|
b[--i] = '\0';
|
|
}
|
|
|
|
while (i-- > 0)
|
|
{
|
|
if (b[i] == '+' || b[i] == '-')
|
|
{
|
|
break;
|
|
}
|
|
if (b[i] == ':' || b[i] == ' ')
|
|
{
|
|
i = 0;
|
|
break; /* No time zone found */
|
|
}
|
|
}
|
|
|
|
if (i == 0)
|
|
{
|
|
e = l;
|
|
}
|
|
else
|
|
{
|
|
timezone = YES;
|
|
e = i;
|
|
if (isdigit(b[i-1]))
|
|
{
|
|
/*
|
|
* Make space between seconds and timezone.
|
|
*/
|
|
memmove(&b[i+1], &b[i], l - i);
|
|
b[i++] = ' ';
|
|
b[++l] = '\0';
|
|
}
|
|
|
|
/*
|
|
* Ensure we have a four digit timezone value.
|
|
*/
|
|
if (isdigit(b[i+1]) && isdigit(b[i+2]))
|
|
{
|
|
if (b[i+3] == '\0')
|
|
{
|
|
// Two digit time zone ... append zero minutes
|
|
b[l++] = '0';
|
|
b[l++] = '0';
|
|
b[l] = '\0';
|
|
}
|
|
else if (b[i+3] == ':')
|
|
{
|
|
// Zone with colon before minutes ... remove it
|
|
b[i+3] = b[i+4];
|
|
b[i+4] = b[i+5];
|
|
b[--l] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
/* kludge for timestamps with fractional second information.
|
|
* Force it to 3 digit millisecond */
|
|
while (i-- > 0)
|
|
{
|
|
if (b[i] == '.')
|
|
{
|
|
milliseconds = YES;
|
|
i++;
|
|
if (!isdigit(b[i]))
|
|
{
|
|
memmove(&b[i+3], &b[i], e-i);
|
|
l += 3;
|
|
memcpy(&b[i], "000", 3);
|
|
}
|
|
i++;
|
|
if (!isdigit(b[i]))
|
|
{
|
|
memmove(&b[i+2], &b[i], e-i);
|
|
l += 2;
|
|
memcpy(&b[i], "00", 2);
|
|
}
|
|
i++;
|
|
if (!isdigit(b[i]))
|
|
{
|
|
memmove(&b[i+1], &b[i], e-i);
|
|
l += 1;
|
|
memcpy(&b[i], "0", 1);
|
|
}
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
if (i > 0 && i < e)
|
|
{
|
|
memmove(&b[i], &b[e], l - e);
|
|
l -= (e - i);
|
|
}
|
|
b[l] = '\0';
|
|
if (l == 0)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
s = [NSString stringWithUTF8String: b];
|
|
|
|
if (YES == timezone)
|
|
{
|
|
if (milliseconds == YES)
|
|
{
|
|
d = [NSCalendarDate dateWithString: s
|
|
calendarFormat: @"%Y-%m-%d %H:%M:%S.%F %z"
|
|
locale: nil];
|
|
}
|
|
else
|
|
{
|
|
d = [NSCalendarDate dateWithString: s
|
|
calendarFormat: @"%Y-%m-%d %H:%M:%S %z"
|
|
locale: nil];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (milliseconds == YES)
|
|
{
|
|
d = [NSCalendarDate dateWithString: s
|
|
calendarFormat: @"%Y-%m-%d %H:%M:%S.%F"
|
|
locale: nil];
|
|
}
|
|
else
|
|
{
|
|
d = [NSCalendarDate dateWithString: s
|
|
calendarFormat: @"%Y-%m-%d %H:%M:%S"
|
|
locale: nil];
|
|
}
|
|
}
|
|
[d setCalendarFormat: @"%Y-%m-%d %H:%M:%S %z"];
|
|
return d;
|
|
}
|
|
|
|
- (NSString*) dbToStringFromBuffer: (char*)b length: (int)l
|
|
{
|
|
NSData *d;
|
|
NSString *s;
|
|
|
|
/*
|
|
* Database fields are padded to the full field size with spaces or nuls ...
|
|
* we need to remove that padding before placing in a string.
|
|
*/
|
|
while (l > 0 && b[l-1] <= ' ')
|
|
{
|
|
l--;
|
|
}
|
|
d = [[NSData alloc] initWithBytes: b length: l];
|
|
s = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding];
|
|
[d release];
|
|
return [s autorelease];
|
|
}
|
|
|
|
@end
|
|
|