libs-sqlclient/ECPG.pgm
Richard Frith-MacDonald 0c1eebfc6c 64bit printf format fixup
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@37063 72102866-910b-0410-8b05-ffd578937521
2013-09-10 12:42:41 +00:00

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