libs-sqlclient/Oracle.pm
Richard Frith-MacDonald 0469ba48fb Fix timestamps without timezone to use local time zone
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@36655 72102866-910b-0410-8b05-ffd578937521
2013-05-24 14:20:29 +00:00

1105 lines
26 KiB
Objective-C

/* -*-objc-*- */
/** Implementation of SQLClientOracle for GNUStep
Copyright (C) 2004 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <rfm@gnu.org>
Written by: Nicola Pero <nicola@brainsrtorm.co.uk>
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 "SQLClient.h"
/*
* Example configuration for an Oracle database:
*
* oracle-test = {
* ServerType = "Oracle"
* SQLDatabase = "nicola";
* SQLPassword = "mbrand";
* SQLUser = "mbrand";
* };
*
* Where SQLDatabase is the Unique database Identifier.
*
*/
@interface SQLClientOracle : SQLClient
@end
@interface SQLClientOracle(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
EXEC SQL INCLUDE sqlca;
EXEC SQL WHENEVER SQLERROR DO SQLClientOracleErrorHandler();
/**
* Return YES of the last SQL error indicated we are out of data,
* NO otherwise.
*/
BOOL SQLClientOracleOutOfData()
{
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 SQLClientOracleErrorHandler()
{
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 (@"(Oracle) Raising an exception, %ld, %s",
code, sqlca.sqlerrm.sqlerrmc);
if (strncmp(ptr, e0, strlen(e0)) == 0
|| strncmp(ptr, e1, strlen(e1)) == 0)
{
[NSException raise: SQLConnectionException
format: @"(Oracle) SQL Error: SQLCODE=(%ld): %s", code, ptr];
}
else
{
[NSException raise: SQLException
format: @"(Oracle) SQL Error: SQLCODE=(%ld): %s", code, ptr];
}
}
@implementation SQLClientOracle
- (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 is the Oracle Net identifier for the database. */
database_c = [[self database] UTF8String];
/* User and password are used to connect to the database. */
user_c = [[self user] UTF8String];
password_c = [[self password] UTF8String];
/* Client is only used to give this connection a name
* and distinguish it from other connections.
*/
client_c = [[self clientName] UTF8String];
if (c != 0)
{
[self debug: @"(Oracle) Connect to database %s user %s as %s",
database_c, user_c, client_c];
}
EXEC SQL CONNECT :user_c IDENTIFIED BY :password_c
AT :client_c USING :database_c;
if (c != 0)
{
[self debug: @"(Oracle) Connected (%s)", client_c];
}
connected = YES;
}
NS_HANDLER
{
[self error: @"(Oracle) Error connecting to database: %@",
localException];
}
NS_ENDHANDLER
}
else
{
[self error:
@"(Oracle) Connect with no user/password/database configured"];
}
}
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];
[self debug: @"(Oracle) Disconnecting client %@", [self clientName]];
/* To disconnect from the database, we issuse a COMMIT
* statement with the RELEASE option. The RELEASE option
* causes it to disconnect after the COMMIT.
*/
EXEC SQL AT :client_c COMMIT WORK RELEASE;
[self debug: @"(Oracle) Disconnected client %@", [self clientName]];
}
NS_HANDLER
{
[self error: @"(Oracle) 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;
CREATE_AUTORELEASE_POOL(arp);
NSString *stmt = [info objectAtIndex: 0];
unsigned int length;
BOOL manuallyAutoCommit = NO;
length = [stmt length];
if (length == 0)
{
[NSException raise: NSInternalInconsistencyException
format: @"(Oracle) Statement produced null string"];
}
statement = (char*)[stmt UTF8String];
handle = (char*)[[self clientName] UTF8String];
/*
* Ensure we have a working connection.
*/
if ([self connect] == NO)
{
[NSException raise: SQLException
format: @"(Oracle) Unable to connect to database"];
}
NS_DURING
{
if ([self isInTransaction] == NO)
{
manuallyAutoCommit = YES;
}
EXEC SQL AT :handle PREPARE command FROM :statement;
EXEC SQL AT :handle EXECUTE command;
if (manuallyAutoCommit)
{
EXEC SQL AT :handle COMMIT;
}
}
NS_HANDLER
{
NSString *n = [localException name];
NSString *msg = [localException reason];
if (manuallyAutoCommit)
{
EXEC SQL AT :handle ROLLBACK;
}
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];
}
}
[self error: @"(Oracle) Error executing statement:\n%@\n%@",
stmt, localException];
[localException raise];
}
NS_ENDHANDLER
DESTROY(arp);
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 recordClass: (Class)rClass
{
EXEC SQL BEGIN DECLARE SECTION;
int count;
int index;
short int indicator;
int type;
int length;
int octetLength;
short int returnedOctetLength;
char fieldName[120];
char *aString;
/* This holds a string representation of numbers returned by Oracle.
* 128 seems a safe bound - else they'll be truncated. */
char aNumber[128];
char *query;
char *handle;
EXEC SQL END DECLARE SECTION;
CREATE_AUTORELEASE_POOL(arp);
NSMutableArray *records;
BOOL isOpen = NO;
BOOL wasInTransaction = [self isInTransaction];
BOOL allocatedDescriptor = NO;
length = [stmt length];
if (length == 0)
{
[NSException raise: NSInternalInconsistencyException
format: @"(Oracle) Statement produced null string"];
}
handle = (char*)[[self clientName] UTF8String];
query = (char*)[stmt UTF8String];
records = [[NSMutableArray alloc] initWithCapacity: 32];
/*
* Ensure we have a working connection.
*/
if ([self connect] == NO)
{
[NSException raise: SQLException
format: @"(Oracle) Unable to connect to database"];
}
NS_DURING
{
/* This is really the output descriptor. We do not use input
* descriptors; all the input is in the SQL statement.
*/
EXEC SQL ALLOCATE DESCRIPTOR 'myDesc';
allocatedDescriptor = YES;
EXEC SQL AT :handle PREPARE myQuery from :query;
if ([self isInTransaction] == NO)
{
/* EXEC SQL AT :handle BEGIN; */
_inTransaction = YES;
}
EXEC SQL AT :handle DECLARE myCursor CURSOR FOR myQuery;
EXEC SQL AT :handle OPEN myCursor;
isOpen = YES;
EXEC SQL AT :handle DESCRIBE OUTPUT myQuery USING DESCRIPTOR 'myDesc';
EXEC SQL GET DESCRIPTOR 'myDesc' :count = COUNT;
if (count > 0)
{
/* Now we do what the Oracle examples do, which is we forcefully
* require to the library to convert everything into types
* chosen by us (mostly strings). The reason we do it is that
* managing the 'internal' Oracle datatypes is a daunting task
* (for example numbers are returned in a 22 byte representation
* used internally by Oracle ...) and apparently it's now how
* they expect you to use it - they provide no examples or
* explanations of how to do it btw! They expect you to choose
* which 'external' Oracle representation you want, by using SET
* DESCRIPTOR as we do here, and then FETCH comfortably data
* which is returned in the representation you chose. So we do
* that way.
*/
int originalType[count];
for (index = 1; index <= count; index++)
{
EXEC SQL GET DESCRIPTOR 'myDesc' VALUE :index
:length = LENGTH,
:octetLength = OCTET_LENGTH,
:type = TYPE;
/* Save the original type so that we know later what's
* inside each returned value. */
originalType[index - 1] = type;
switch (type)
{
/* Negative values of 'type' are used for Oracle
* proprietary extensions; positive values for ANSI
* types. */
/* We get character types as they are. */
case 1 /* CHARACTER */:
case 12 /* CHARACTER_VARYING */:
case -1 /* Oracle VARCHAR2 */:
type = -1; /* Oracle VARCHAR2 */
EXEC SQL SET DESCRIPTOR 'myDesc' VALUE :index
TYPE = :type;
break;
/* We get a string representation (128 bytes long)
* of any number. */
case 2 /* NUMERIC */:
case 3 /* DECIMAL */:
case 4 /* INTEGER */:
case 5 /* SMALLINT*/:
case 6 /* FLOAT */:
case 7 /* REAL */:
case 8 /* DOUBLE_PRECISION */:
type = 12; /* ANSI CHARACTER_VARYING */
octetLength = 128;
EXEC SQL SET DESCRIPTOR 'myDesc' VALUE :index
LENGTH = :octetLength,
TYPE = :type;
break;
}
}
while (1)
{
SQLRecord *record;
id keys[count];
id values[count];
EXEC SQL AT :handle FETCH myCursor INTO SQL DESCRIPTOR 'myDesc';
if (sqlca.sqlcode)
{
break;
}
for (index = 1; index <= count; ++index)
{
id v;
EXEC SQL GET DESCRIPTOR 'myDesc' VALUE :index
:indicator = INDICATOR,
:length = LENGTH,
:fieldName = NAME,
:octetLength = OCTET_LENGTH,
:returnedOctetLength = RETURNED_OCTET_LENGTH,
:type = TYPE;
if (indicator == -1)
{
v = [NSNull null];
}
else
{
switch (originalType[index - 1])
{
case 3 /* DECIMAL */:
case 4 /* INTEGER */:
case 5 /* SMALLINT*/:
{
int aInt;
EXEC SQL GET DESCRIPTOR 'myDesc' VALUE :index
:aNumber = DATA;
aInt = [[NSString stringWithUTF8String: aNumber] intValue];
v = [NSNumber numberWithInt: aInt];
break;
}
case 2 /* NUMERIC */:
case 6 /* FLOAT */:
case 7 /* REAL */:
case 8 /* DOUBLE_PRECISION */:
{
float aFloat;
EXEC SQL GET DESCRIPTOR 'myDesc' VALUE :index
:aNumber = DATA;
aFloat = [[NSString stringWithUTF8String: aNumber] floatValue];
v = [NSNumber numberWithFloat: aFloat];
break;
}
case 1 /* CHARACTER */:
case 12 /* CHARACTER_VARYING */:
case -1 /* Oracle VARCHAR2 */:
/* For unclear reasons, returnedOctetLength is always 0. */
/* This code (patchy and experimentally
* determined) really works if the database
* field contains something like UTF-8,
* returned as UTF-8 (such as for CHAR(20)
* fields). If UNICODE stuff is returned,
* then it's not the right way. We might
* need to make a different depending on the
* originalField type.
*/
/* Add 1 byte to \0-pad the string. */
aString = malloc (octetLength + 1);
if (aString == NULL)
{
[NSException
raise: @"OutOfMemoryException"
format: @"(Oracle) could not malloc %d bytes",
octetLength];
}
EXEC SQL GET DESCRIPTOR 'myDesc' VALUE :index
:aString = DATA;
/* \0-pad the string. */
aString[octetLength] = '\0';
if (YES == _shouldTrim)
{
trim (aString);
}
v = [NSString stringWithUTF8String: aString];
free(aString);
break;
/* TODO: DATES */
/*
TODO TODO
case BLOB:
EXEC SQL GET DESCRIPTOR 'myDesc' VALUE :index
:aString = DATA;
v = [self dataFromBlob: aString];
free(aString);
break;
*/
default:
aString = malloc (octetLength + 1);
if (aString == NULL)
{
[NSException
raise: @"OutOfMemoryException"
format: @"(Oracle) could not malloc %d bytes",
octetLength];
}
EXEC SQL GET DESCRIPTOR 'myDesc' VALUE :index
:aString = DATA;
aString[octetLength] = '\0';
if (YES == _shouldTrim)
{
trim (aString);
}
v = [NSString stringWithUTF8String: aString];
free (aString);
NSLog(@"(Oracle) Unknown data type (%d) for '%s': '%@'",
type, fieldName, v);
break;
}
}
values[index - 1] = v;
keys[index - 1] = [NSString stringWithUTF8String:
fieldName];
}
record = [rClass newWithValues: values
keys: keys
count: count];
[records addObject: record];
RELEASE(record);
}
}
isOpen = NO;
EXEC SQL AT :handle CLOSE myCursor;
if (wasInTransaction == NO && [self isInTransaction] == YES)
{
EXEC SQL AT :handle COMMIT;
_inTransaction = NO;
}
EXEC SQL DEALLOCATE DESCRIPTOR 'myDesc';
allocatedDescriptor = NO;
}
NS_HANDLER
{
NSString *n = [localException name];
NSString *msg = [localException reason];
DESTROY(records);
NS_DURING
{
if (isOpen == YES)
{
EXEC SQL AT :handle CLOSE myCursor;
}
if (wasInTransaction == NO && [self isInTransaction] == YES)
{
EXEC SQL AT :handle ROLLBACK;
_inTransaction = NO;
}
}
NS_HANDLER
{
NSString *e = [localException name];
if (wasInTransaction == NO && [self isInTransaction] == YES)
{
_inTransaction = NO;
}
if ([e isEqual: SQLConnectionException] == YES)
{
[self disconnect];
}
}
NS_ENDHANDLER
NS_DURING
{
if (allocatedDescriptor)
{
EXEC SQL DEALLOCATE DESCRIPTOR 'myDesc';
allocatedDescriptor = NO;
}
}
NS_HANDLER
{
NSLog (@"Can't deallocate descriptor ... serious problem.");
}
NS_ENDHANDLER
if ([n isEqual: SQLConnectionException] == YES)
{
_inTransaction = NO;
[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];
}
}
RETAIN(localException);
RELEASE(arp);
AUTORELEASE(localException);
[localException raise];
}
NS_ENDHANDLER
DESTROY(arp);
return AUTORELEASE(records);
}
/**
* Convert NSData object with raw binary data into escaped sequence
*/
- (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 dst; // Owned by autoreleased NSMutableData
}
/**
* Convert escaped sequence to raw binary data in NSData object
*/
- (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;
}
/**
* Convert an NSdate into a buffer for sending to the database.
* Return YES if the conversion fitted, NO if it was truncated.
* The value of l is expected to be one less than the size of the buffer.
* A nul character is appended to the bytes in the buffer.
*/
- (BOOL) dbFromDate: (NSDate*)d toBuffer: (char*)b length: (int)l
{
NSString *s;
s = [d descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z"
timeZone: nil
locale: nil];
return [self dbFromString: s toBuffer: b length: l];
}
/**
* Convert an NSString into a buffer for sending to the database.<br />
* Return YES if the conversion fitted, NO if it was truncated.<br />
* If s is nil, it is treated as an empty string.<br />
* The value of l is expected to be one less than the size of the buffer
* and must be at least 1.<br />
* The pointer b must not be null.<br />
* A nul character is appended to the bytes in the buffer.<br />
* Raises an exception when passed invalid arguments.
*/
- (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: @"(Oracle) -%@: length too small (%d)",
NSStringFromSelector(_cmd), l];
}
if (b == 0)
{
[NSException raise: NSInvalidArgumentException
format: @"(Oracle) -%@: 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;
}
/**
* Convert from a database character buffer to an NSString.
*/
- (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];
RELEASE(d);
return AUTORELEASE(s);
}
@end