mirror of
https://github.com/gnustep/libs-sqlclient.git
synced 2025-02-14 15:40:59 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@36655 72102866-910b-0410-8b05-ffd578937521
1105 lines
26 KiB
Objective-C
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
|
|
|