mirror of
https://github.com/gnustep/libs-sqlclient.git
synced 2025-02-11 08:30:42 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@39398 72102866-910b-0410-8b05-ffd578937521
1942 lines
45 KiB
Objective-C
1942 lines
45 KiB
Objective-C
/* -*-objc-*- */
|
|
|
|
/** Implementation of SQLClientJDBC for GNUStep
|
|
Copyright (C) 2006 Free Software Foundation, Inc.
|
|
|
|
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
|
Date: August 2006
|
|
|
|
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: 2006-06-04 10:19:28 +0100 (Sun, 04 Jun 2006) $ $Revision: 23028 $
|
|
*/
|
|
|
|
#import <Foundation/NSAutoreleasePool.h>
|
|
#import <Foundation/NSCalendarDate.h>
|
|
#import <Foundation/NSCharacterSet.h>
|
|
#import <Foundation/NSData.h>
|
|
#import <Foundation/NSDate.h>
|
|
#import <Foundation/NSDictionary.h>
|
|
#import <Foundation/NSException.h>
|
|
#import <Foundation/NSLock.h>
|
|
#import <Foundation/NSMapTable.h>
|
|
#import <Foundation/NSNotification.h>
|
|
#import <Foundation/NSNotification.h>
|
|
#import <Foundation/NSNull.h>
|
|
#import <Foundation/NSProcessInfo.h>
|
|
#import <Foundation/NSString.h>
|
|
#import <Foundation/NSThread.h>
|
|
#import <Foundation/NSUserDefaults.h>
|
|
#import <Foundation/NSValue.h>
|
|
|
|
#import <Performance/GSTicker.h>
|
|
|
|
#include "config.h"
|
|
|
|
#define SQLCLIENT_PRIVATE @public
|
|
|
|
#include "SQLClient.h"
|
|
|
|
@interface _JDBCTransaction : SQLTransaction
|
|
@end
|
|
|
|
#include <jni.h>
|
|
|
|
static NSString *JDBCException = @"SQLClientJDBCException";
|
|
|
|
/*
|
|
* Cache connection information
|
|
*/
|
|
typedef struct {
|
|
jobject connection;
|
|
jmethodID commit;
|
|
jmethodID rollback;
|
|
jmethodID prepare;
|
|
jobject statement;
|
|
jmethodID executeUpdate;
|
|
jmethodID executeQuery;
|
|
jmethodID addBatch;
|
|
jmethodID clearBatch;
|
|
jmethodID executeBatch;
|
|
} JInfo;
|
|
|
|
|
|
/* SQLClientJVM shamelessly stolen from JIGS ... written by Nicola Pero
|
|
* and copyright the Free Software Foundation.
|
|
*/
|
|
|
|
@interface SQLClientJVM : NSObject
|
|
{
|
|
}
|
|
+ (void) startVirtualMachineWithClassPath: (NSString *)classPath
|
|
libraryPath: (NSString *)libraryPath;
|
|
+ (void) destroyVirtualMachine;
|
|
+ (BOOL) isVirtualMachineRunning;
|
|
+ (NSString *) defaultClassPath;
|
|
+ (NSString *) defaultLibraryPath;
|
|
+ (void) attachCurrentThread;
|
|
+ (void) detachCurrentThread;
|
|
+ (void) registerJavaVM: (JavaVM *)javaVMHandle;
|
|
@end
|
|
|
|
/*
|
|
* A fast function to get the (JNIEnv *) variable.
|
|
*/
|
|
static JNIEnv *SQLClientJNIEnv ();
|
|
|
|
static JavaVM *SQLClientJavaVM = NULL;
|
|
|
|
/*
|
|
* Return the (JNIEnv *) associated with the current thread,
|
|
* or NULL if no java virtual machine is running (or if the thread
|
|
* is not attached to the JVM).
|
|
*
|
|
* NB: This function performs a call. Better use your (JNIEnv *) if
|
|
* you already have it.
|
|
*
|
|
*/
|
|
JNIEnv *SQLClientJNIEnv ()
|
|
{
|
|
JNIEnv *penv;
|
|
|
|
if ((*SQLClientJavaVM)->GetEnv (SQLClientJavaVM, (void **)&penv,
|
|
JNI_VERSION_1_2) == JNI_OK)
|
|
{
|
|
return penv;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
@implementation SQLClientJVM (GNUstepInternals)
|
|
+ (void) _attachCurrentThread: (NSNotification *)not
|
|
{
|
|
[self attachCurrentThread];
|
|
}
|
|
|
|
+ (void) _detachCurrentThread: (NSNotification *)not
|
|
{
|
|
[self detachCurrentThread];
|
|
}
|
|
@end
|
|
|
|
@implementation SQLClientJVM
|
|
|
|
+ (void) startVirtualMachineWithClassPath: (NSString *)classPath
|
|
libraryPath: (NSString *)libraryPath
|
|
{
|
|
JavaVMInitArgs jvm_args;
|
|
JavaVMOption options[32];
|
|
int args = 0;
|
|
jint result;
|
|
JNIEnv *env;
|
|
NSString *path;
|
|
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
|
|
|
if (SQLClientJavaVM != NULL)
|
|
{
|
|
[NSException raise: NSGenericException
|
|
format: @"Only one Java Virtual Machine "
|
|
@"can be running at each time"];
|
|
}
|
|
|
|
// If we don't pass these options, it assumes they are really @""
|
|
if (classPath == nil)
|
|
{
|
|
classPath = [self defaultClassPath];
|
|
if (classPath == nil)
|
|
{
|
|
classPath = @"";
|
|
}
|
|
}
|
|
if (libraryPath == nil)
|
|
{
|
|
libraryPath = [self defaultLibraryPath];
|
|
if (libraryPath == nil)
|
|
{
|
|
libraryPath = @"";
|
|
}
|
|
}
|
|
|
|
path = [NSString stringWithFormat: @"-Djava.library.path=%@", libraryPath];
|
|
options[args].optionString = strdup([path UTF8String]);
|
|
options[args++].extraInfo = 0;
|
|
|
|
path = [NSString stringWithFormat: @"-Djava.class.path=%@", classPath];
|
|
options[args].optionString = strdup([path UTF8String]);
|
|
options[args++].extraInfo = 0;
|
|
|
|
path = [NSString stringWithFormat: @"-Xbootclasspath/a:%@", classPath];
|
|
options[args].optionString = strdup([path UTF8String]);
|
|
options[args++].extraInfo = 0;
|
|
|
|
options[args].optionString = "-verbose:class,jni";
|
|
options[args++].extraInfo = 0;
|
|
|
|
jvm_args.nOptions = args;
|
|
jvm_args.version = JNI_VERSION_1_2;
|
|
jvm_args.options = options;
|
|
jvm_args.ignoreUnrecognized = JNI_FALSE;
|
|
|
|
result = JNI_CreateJavaVM (&SQLClientJavaVM, (void **)&env, &jvm_args);
|
|
if (result < 0)
|
|
{
|
|
[NSException raise: NSGenericException
|
|
format: @"Could not start Java Virtual Machine"];
|
|
}
|
|
|
|
/* Whenever a thread start or ends, we want to automatically attach
|
|
or detach it to/from the JVM */
|
|
[nc addObserver: self selector: @selector (_attachCurrentThread:)
|
|
name: NSThreadDidStartNotification object: nil];
|
|
|
|
[nc addObserver: self selector: @selector (_detachCurrentThread:)
|
|
name: NSThreadWillExitNotification object: nil];
|
|
|
|
return;
|
|
}
|
|
|
|
+ (void) destroyVirtualMachine
|
|
{
|
|
jint result;
|
|
|
|
if (SQLClientJavaVM == NULL)
|
|
{
|
|
[NSException raise: NSGenericException
|
|
format: @"destroyJVM called without a JVM running"];
|
|
}
|
|
result = (*SQLClientJavaVM)->DestroyJavaVM (SQLClientJavaVM);
|
|
if (result < 0)
|
|
{
|
|
[NSException raise: NSGenericException
|
|
format: @"Could not destroy Java Virtual Machine"];
|
|
}
|
|
else
|
|
{
|
|
SQLClientJavaVM = NULL;
|
|
}
|
|
}
|
|
|
|
+ (BOOL) isVirtualMachineRunning
|
|
{
|
|
if (SQLClientJavaVM == NULL)
|
|
{
|
|
return NO;
|
|
}
|
|
else
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
+ (NSString *) defaultClassPath
|
|
{
|
|
NSDictionary *environment = [[NSProcessInfo processInfo] environment];
|
|
|
|
return [environment objectForKey: @"CLASSPATH"];
|
|
}
|
|
|
|
+ (NSString *) defaultLibraryPath
|
|
{
|
|
NSDictionary *environment = [[NSProcessInfo processInfo] environment];
|
|
|
|
return [environment objectForKey: @"LD_LIBRARY_PATH"];
|
|
}
|
|
|
|
+ (void) attachCurrentThread
|
|
{
|
|
static int count = 0;
|
|
JNIEnv *env;
|
|
JavaVMAttachArgs args;
|
|
jint result;
|
|
|
|
if (SQLClientJavaVM == NULL)
|
|
{
|
|
/* No JVM - nothing to do */
|
|
return;
|
|
}
|
|
|
|
if (SQLClientJNIEnv () != NULL)
|
|
{
|
|
/* The thread is already attached */
|
|
return;
|
|
}
|
|
|
|
{
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
|
|
args.version = JNI_VERSION_1_2;
|
|
args.name = (char *)[[NSString stringWithFormat:
|
|
@"GNUstepThread-%d", count] cString];
|
|
args.group = NULL;
|
|
|
|
result = (*SQLClientJavaVM)->AttachCurrentThread
|
|
(SQLClientJavaVM, (void **)&env, &args);
|
|
|
|
[pool release];
|
|
}
|
|
|
|
if (result < 0)
|
|
{
|
|
[NSException raise: NSGenericException
|
|
format: @"Could not attach thread to the Java VM"];
|
|
}
|
|
|
|
count++;
|
|
if (count > 100000)
|
|
{
|
|
/* Duplicated names shouldn't cause any problem */
|
|
count = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
+ (void) detachCurrentThread
|
|
{
|
|
jint result;
|
|
|
|
if (SQLClientJavaVM == NULL)
|
|
{
|
|
/* No JVM - nothing to do */
|
|
return;
|
|
}
|
|
|
|
if (SQLClientJNIEnv () == NULL)
|
|
{
|
|
/* The thread is not attached */
|
|
return;
|
|
}
|
|
|
|
result = (*SQLClientJavaVM)->DetachCurrentThread (SQLClientJavaVM);
|
|
|
|
if (result < 0)
|
|
{
|
|
[NSException raise: NSGenericException
|
|
format: @"Could not detach thread from the Java VM"];
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
+ (void) registerJavaVM: (JavaVM *)javaVMHandle
|
|
{
|
|
if (javaVMHandle == NULL)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Trying to register a NULL Java VM"];
|
|
}
|
|
|
|
if (SQLClientJavaVM != NULL)
|
|
{
|
|
if (javaVMHandle == SQLClientJavaVM)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
[NSException raise: NSGenericException
|
|
format: @"Trying to register a Java VM "
|
|
@"while one is already running"];
|
|
}
|
|
}
|
|
|
|
SQLClientJavaVM = javaVMHandle;
|
|
|
|
// Safety check. If javaVMHandle is invalid, the following will crash
|
|
// your app. The app would crash anyway later on, so it's better to crash
|
|
// it here, where it is easier to debug.
|
|
SQLClientJNIEnv ();
|
|
|
|
return;
|
|
}
|
|
|
|
@end
|
|
|
|
static jstring
|
|
JStringFromNSString (JNIEnv *env, NSString *string)
|
|
{
|
|
jstring javaString;
|
|
int length = [string length];
|
|
|
|
/* We allocate strings of up to 10k on the stack - others using
|
|
malloc. */
|
|
if (length < 10000)
|
|
{
|
|
unichar uniString[length];
|
|
|
|
// Get a unicode representation of the string in the buffer
|
|
[string getCharacters: uniString];
|
|
|
|
// Create a java string using the buffer
|
|
javaString = (*env)->NewString (env, uniString, length);
|
|
// NB: if javaString is NULL, an exception has been thrown.
|
|
}
|
|
else
|
|
{
|
|
unichar *uniString;
|
|
|
|
uniString = malloc (sizeof (unichar) * length);
|
|
[string getCharacters: uniString];
|
|
javaString = (*env)->NewString (env, uniString, length);
|
|
free (uniString);
|
|
}
|
|
|
|
return javaString;
|
|
}
|
|
|
|
static NSString*
|
|
NSStringFromJString (JNIEnv *env, jstring string)
|
|
{
|
|
unichar *uniString;
|
|
jsize length;
|
|
NSString *gnustepString;
|
|
|
|
// Get a Unicode string from the jstring
|
|
uniString = (unichar *)(*env)->GetStringChars (env, string, NULL);
|
|
if (uniString == NULL)
|
|
{
|
|
// OutOfMemoryError thrown
|
|
return NULL;
|
|
}
|
|
|
|
// Get the Unicode string length
|
|
length = (*env)->GetStringLength (env, string);
|
|
|
|
// Create a GNUstep string from the Unicode string
|
|
gnustepString = [NSString stringWithCharacters: uniString length: length];
|
|
|
|
// Release the temporary string
|
|
(*env)->ReleaseStringChars (env, string, uniString);
|
|
|
|
return gnustepString;
|
|
}
|
|
|
|
static NSData *
|
|
NSDataFromByteArray (JNIEnv *env, jbyteArray array)
|
|
{
|
|
NSData *returnData;
|
|
jbyte *bytes;
|
|
unsigned length;
|
|
|
|
length = (*env)->GetArrayLength (env, array);
|
|
|
|
bytes = (*env)->GetByteArrayElements (env, array, NULL);
|
|
if (bytes == NULL)
|
|
{
|
|
/* OutOfMemoryError */
|
|
return nil;
|
|
}
|
|
|
|
returnData = [NSData dataWithBytes: bytes length: length];
|
|
|
|
(*env)->ReleaseByteArrayElements (env, array, bytes, 0);
|
|
|
|
return returnData;
|
|
}
|
|
|
|
static jbyteArray
|
|
ByteArrayFromNSData (JNIEnv *env, NSData *data)
|
|
{
|
|
const jbyte *bytes;
|
|
unsigned length;
|
|
jbyteArray javaArray;
|
|
|
|
length = [data length];
|
|
bytes = [data bytes];
|
|
|
|
javaArray = (*env)->NewByteArray (env, length);
|
|
if (javaArray == NULL)
|
|
{
|
|
/* OutOfMemory exception thrown */
|
|
return NULL;
|
|
}
|
|
|
|
(*env)->SetByteArrayRegion (env, javaArray, 0, length, (jbyte *)bytes);
|
|
if ((*env)->ExceptionCheck (env))
|
|
{
|
|
/* No reason for this to happen - except a bug in NSData */
|
|
return NULL;
|
|
}
|
|
|
|
return javaArray;
|
|
}
|
|
|
|
|
|
|
|
static NSString *JExceptionClear (JNIEnv *env)
|
|
{
|
|
NSString *desc = nil;
|
|
jthrowable exc = (*env)->ExceptionOccurred (env);
|
|
|
|
if (exc != NULL)
|
|
{
|
|
static jclass java_lang_Exception = NULL;
|
|
jmethodID jid = NULL;
|
|
jstring jstr = NULL;
|
|
|
|
// (*env)->ExceptionDescribe (env);
|
|
|
|
// We need to clear the exception before doing anything else.
|
|
(*env)->ExceptionClear (env);
|
|
|
|
java_lang_Exception = (*env)->FindClass(env, "java/lang/Exception");
|
|
|
|
if (java_lang_Exception == NULL)
|
|
{
|
|
(*env)->DeleteLocalRef (env, exc);
|
|
(*env)->ExceptionDescribe (env);
|
|
desc = @"Could not get global reference to "
|
|
@"java/lang/Exception to describe exception";
|
|
goto done;
|
|
}
|
|
|
|
jid = (*env)->GetMethodID (env, java_lang_Exception, "getMessage",
|
|
"()Ljava/lang/String;");
|
|
if (jid == NULL)
|
|
{
|
|
(*env)->DeleteLocalRef (env, exc);
|
|
desc = @"Could not get the jmethodID of getMessage"
|
|
@"of java/lang/Exception to describe exception";
|
|
goto done;
|
|
}
|
|
|
|
if ((*env)->PushLocalFrame (env, 1) < 0)
|
|
{
|
|
(*env)->DeleteLocalRef (env, exc);
|
|
(*env)->ExceptionDescribe (env);
|
|
desc = @"Could not create enough JNI local references "
|
|
@"to get a description of the exception";
|
|
goto done;
|
|
}
|
|
|
|
// Get the message
|
|
jstr = (*env)->CallObjectMethod (env, exc, jid);
|
|
if ((*env)->ExceptionOccurred (env))
|
|
{
|
|
(*env)->ExceptionDescribe (env);
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
desc = @"Exception occurred while getting a description of exception";
|
|
goto done;
|
|
}
|
|
|
|
(*env)->DeleteLocalRef (env, exc);
|
|
if ((*env)->ExceptionOccurred (env))
|
|
{
|
|
(*env)->ExceptionDescribe (env);
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
desc = @"Exception occurred while getting a description of exception";
|
|
goto done;
|
|
}
|
|
|
|
if (jstr == NULL) // Oh oh - something really wrong here
|
|
{
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
desc = @"NULL description of exception";
|
|
goto done;
|
|
}
|
|
|
|
desc = NSStringFromJString (env, jstr);
|
|
if (desc == nil)
|
|
{
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
desc = @"Exception while converting string of exception";
|
|
}
|
|
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
}
|
|
done:
|
|
return desc;
|
|
}
|
|
|
|
// Throw an exception if one occurred
|
|
static void JException (JNIEnv *env)
|
|
{
|
|
NSString *text = JExceptionClear (env);
|
|
|
|
if (text != nil)
|
|
{
|
|
[NSException raise: JDBCException format: @"%@", text];
|
|
}
|
|
}
|
|
|
|
|
|
static NSDate* NSDateFromNSString (NSString *s)
|
|
{
|
|
NSDate *d;
|
|
char b[32];
|
|
BOOL milliseconds = NO;
|
|
int l;
|
|
int i;
|
|
|
|
strcpy(b, [s UTF8String]);
|
|
l = strlen(b);
|
|
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];
|
|
}
|
|
else
|
|
{
|
|
int e;
|
|
|
|
/* If it's a simple date (YYYY-MM-DD) append time for start of day. */
|
|
if (l == 10)
|
|
{
|
|
strcat(b, " 00:00:00 +0000");
|
|
l += 15;
|
|
}
|
|
|
|
i = l;
|
|
while (i-- > 0)
|
|
{
|
|
if (b[i] == '+' || b[i] == '-')
|
|
{
|
|
break;
|
|
}
|
|
if (b[i] == ':' || b[i] == ' ')
|
|
{
|
|
i = 0;
|
|
break; /* No time zone found */
|
|
}
|
|
}
|
|
if (i == 0)
|
|
{
|
|
/* A date and time without a timezone ... assume gmt */
|
|
strcpy(b + l, " +0000");
|
|
i = l + 1;
|
|
l += 6;
|
|
}
|
|
|
|
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';
|
|
}
|
|
}
|
|
|
|
/* FIXME ... horrible 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 (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];
|
|
}
|
|
return d;
|
|
}
|
|
}
|
|
|
|
@interface SQLClientJDBC : SQLClient
|
|
@end
|
|
|
|
static NSDate *future = nil;
|
|
static NSNull *null = nil;
|
|
|
|
@implementation SQLClientJDBC
|
|
|
|
static int JDBCDATE = 0;
|
|
static int JDBCTIME = 0;
|
|
static int JDBCTIMESTAMP = 0;
|
|
static int JDBCBOOLEAN = 0;
|
|
static int JDBCBLOB = 0;
|
|
static int JDBCBINARY = 0;
|
|
static int JDBCVARBINARY = 0;
|
|
static int JDBCLONGVARBINARY = 0;
|
|
static int JDBCVARCHAR = 0;
|
|
|
|
+ (void) initialize
|
|
{
|
|
if (future == nil)
|
|
{
|
|
JNIEnv *env;
|
|
jclass jc;
|
|
jfieldID jf;
|
|
|
|
future = [NSCalendarDate dateWithString: @"9999-01-01 00:00:00 +0000"
|
|
calendarFormat: @"%Y-%m-%d %H:%M:%S %z"
|
|
locale: nil];
|
|
[future retain];
|
|
null = [NSNull null];
|
|
[null retain];
|
|
|
|
[SQLClientJVM startVirtualMachineWithClassPath: nil libraryPath: nil];
|
|
env = SQLClientJNIEnv();
|
|
jc = (*env)->FindClass(env, "java/sql/Types");
|
|
JException (env);
|
|
|
|
jf = (*env)->GetStaticFieldID(env, jc, "DATE", "I");
|
|
JException (env);
|
|
JDBCDATE = (*env)->GetStaticIntField(env, jc, jf);
|
|
JException (env);
|
|
|
|
jf = (*env)->GetStaticFieldID(env, jc, "TIME", "I");
|
|
JException (env);
|
|
JDBCTIME = (*env)->GetStaticIntField(env, jc, jf);
|
|
JException (env);
|
|
|
|
jf = (*env)->GetStaticFieldID(env, jc, "TIMESTAMP", "I");
|
|
JException (env);
|
|
JDBCTIMESTAMP = (*env)->GetStaticIntField(env, jc, jf);
|
|
JException (env);
|
|
|
|
jf = (*env)->GetStaticFieldID(env, jc, "BOOLEAN", "I");
|
|
JException (env);
|
|
JDBCBOOLEAN = (*env)->GetStaticIntField(env, jc, jf);
|
|
JException (env);
|
|
|
|
jf = (*env)->GetStaticFieldID(env, jc, "BLOB", "I");
|
|
JException (env);
|
|
JDBCBLOB = (*env)->GetStaticIntField(env, jc, jf);
|
|
JException (env);
|
|
|
|
jf = (*env)->GetStaticFieldID(env, jc, "BINARY", "I");
|
|
JException (env);
|
|
JDBCBINARY = (*env)->GetStaticIntField(env, jc, jf);
|
|
JException (env);
|
|
|
|
jf = (*env)->GetStaticFieldID(env, jc, "VARBINARY", "I");
|
|
JException (env);
|
|
JDBCVARBINARY = (*env)->GetStaticIntField(env, jc, jf);
|
|
JException (env);
|
|
|
|
jf = (*env)->GetStaticFieldID(env, jc, "LONGVARBINARY", "I");
|
|
JException (env);
|
|
JDBCLONGVARBINARY = (*env)->GetStaticIntField(env, jc, jf);
|
|
JException (env);
|
|
|
|
jf = (*env)->GetStaticFieldID(env, jc, "VARCHAR", "I");
|
|
JException (env);
|
|
JDBCVARCHAR = (*env)->GetStaticIntField(env, jc, jf);
|
|
JException (env);
|
|
|
|
}
|
|
}
|
|
|
|
/* Disconnect and deallocate all resources used.
|
|
* Do NOT raise an exception.
|
|
*/
|
|
- (void) _backendDisconnect
|
|
{
|
|
if (extra != 0)
|
|
{
|
|
JNIEnv *env = SQLClientJNIEnv();
|
|
JInfo *ji = (JInfo*)extra;
|
|
jclass jc;
|
|
jmethodID jm;
|
|
|
|
if ((*env)->PushLocalFrame (env, 16) >= 0)
|
|
{
|
|
if (ji->statement != 0)
|
|
{
|
|
jc = (*env)->GetObjectClass(env, ji->statement);
|
|
jm = (*env)->GetMethodID (env, jc, "close", "()V");
|
|
if (jm == 0) JExceptionClear(env);
|
|
else (*env)->CallVoidMethod (env, ji->statement, jm);
|
|
if (jm == 0) JExceptionClear(env);
|
|
(*env)->DeleteGlobalRef (env, ji->statement);
|
|
if (jm == 0) JExceptionClear(env);
|
|
}
|
|
if (ji->connection != 0)
|
|
{
|
|
jc = (*env)->GetObjectClass(env, ji->connection);
|
|
jm = (*env)->GetMethodID (env, jc, "close", "()V");
|
|
if (jm == 0) JExceptionClear(env);
|
|
else (*env)->CallVoidMethod (env, ji->connection, jm);
|
|
if (jm == 0) JExceptionClear(env);
|
|
(*env)->DeleteGlobalRef (env, ji->connection);
|
|
if (jm == 0) JExceptionClear(env);
|
|
}
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
}
|
|
NSZoneFree(NSDefaultMallocZone(), extra);
|
|
extra = 0;
|
|
}
|
|
}
|
|
|
|
- (JInfo*) _backendExtra
|
|
{
|
|
return (JInfo*)extra;
|
|
}
|
|
|
|
- (BOOL) backendConnect
|
|
{
|
|
if (extra == 0)
|
|
{
|
|
connected = NO;
|
|
if ([self database] != nil)
|
|
{
|
|
NSString *dbase = [self database];
|
|
NSRange r;
|
|
|
|
[[self class] purgeConnections: nil];
|
|
|
|
r = [dbase rangeOfString: @":"];
|
|
if (r.length > 0)
|
|
{
|
|
NSString *url;
|
|
NSString *cname;
|
|
JNIEnv *env;
|
|
jclass jc;
|
|
jmethodID jm;
|
|
jobject jo;
|
|
|
|
url = [dbase substringFromIndex: NSMaxRange(r)];
|
|
cname = [dbase substringToIndex: r.location];
|
|
|
|
env = SQLClientJNIEnv();
|
|
if (env == 0)
|
|
{
|
|
NSLog(@"Connect to '%@' failed to set up Java runtime",
|
|
[self name]);
|
|
return NO;
|
|
}
|
|
|
|
/* Ensure the driver for the database is loaded.
|
|
*/
|
|
cname = [cname stringByReplacingString: @"." withString: @"/"];
|
|
if ((*env)->FindClass(env, [cname UTF8String]) == 0)
|
|
{
|
|
JExceptionClear (env);
|
|
NSLog(@"Connect to '%@' failed to load driver '%@'.\n"
|
|
@"Perhaps you need to set your CLASSPATH environment "
|
|
@"variable to point to the location of your JDBC library.",
|
|
[self name], cname);
|
|
return NO;
|
|
}
|
|
|
|
if ((*env)->PushLocalFrame (env, 32) < 0)
|
|
{
|
|
JExceptionClear (env);
|
|
[self debug: @"Connect to '%@' failed memory allocation '%@'",
|
|
[self name], cname];
|
|
return NO;
|
|
}
|
|
|
|
/* Get the driver manager class.
|
|
*/
|
|
jc = (*env)->FindClass(env, "java/sql/DriverManager");
|
|
if (jc == 0)
|
|
{
|
|
JExceptionClear (env);
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
NSLog(@"Connect to '%@' failed to load DriverManager\n"
|
|
@"Perhaps you need to set your CLASSPATH environment "
|
|
@"variable to point to the location of your JDBC library.",
|
|
[self name]);
|
|
return NO;
|
|
}
|
|
|
|
/* Get the method to get a connection.
|
|
*/
|
|
jm = (*env)->GetStaticMethodID(env, jc, "getConnection",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)"
|
|
"Ljava/sql/Connection;");
|
|
if (jm == 0)
|
|
{
|
|
JExceptionClear (env);
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
[self debug: @"Connect to '%@' failed to get connect method",
|
|
[self name]];
|
|
return NO;
|
|
}
|
|
|
|
/* Get the new connection object
|
|
*/
|
|
jobject js1 = JStringFromNSString(env, url);
|
|
jobject js2 = JStringFromNSString(env, [self user]);
|
|
jobject js3 = JStringFromNSString(env, [self password]);
|
|
/*
|
|
NSLog(@"CONNECT '%@', '%@', '%@'",
|
|
NSStringFromJString(env, js1),
|
|
NSStringFromJString(env, js2),
|
|
NSStringFromJString(env, js3));
|
|
*/
|
|
jo = (*env)->CallStaticObjectMethod(env, jc, jm, js1, js2, js3);
|
|
if (jo == 0)
|
|
{
|
|
JExceptionClear (env);
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
[self debug: @"Connect to '%@' failed to get connection",
|
|
[self name]];
|
|
return NO;
|
|
}
|
|
|
|
/* Make a reference so it can't be garbage collected.
|
|
*/
|
|
jo = (*env)->NewGlobalRef(env, jo);
|
|
if (jo == 0)
|
|
{
|
|
JExceptionClear (env);
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
[self debug: @"Connect to '%@' failed to get global ref",
|
|
[self name]];
|
|
return NO;
|
|
}
|
|
else
|
|
{
|
|
JInfo *ji;
|
|
|
|
ji = NSZoneMalloc(NSDefaultMallocZone(), sizeof(JInfo));
|
|
memset(ji, '\0', sizeof(*ji));
|
|
extra = ji;
|
|
|
|
NS_DURING
|
|
{
|
|
ji->connection = jo;
|
|
jc = (*env)->GetObjectClass(env, ji->connection);
|
|
|
|
/* Get the method to set autocommit.
|
|
*/
|
|
jm = (*env)->GetMethodID(env, jc,
|
|
"setAutoCommit", "(Z)V");
|
|
JException (env);
|
|
|
|
/* Turn off autocommit
|
|
*/
|
|
(*env)->CallVoidMethod (env, ji->connection,
|
|
jm, JNI_FALSE);
|
|
JException (env);
|
|
|
|
ji->commit = (*env)->GetMethodID (env, jc,
|
|
"commit", "()V");
|
|
JException(env);
|
|
|
|
ji->rollback = (*env)->GetMethodID (env, jc,
|
|
"rollback", "()V");
|
|
JException(env);
|
|
|
|
ji->prepare = (*env)->GetMethodID (env, jc,
|
|
"prepareStatement",
|
|
"(Ljava/lang/String;)Ljava/sql/PreparedStatement;");
|
|
JException(env);
|
|
|
|
jm = (*env)->GetMethodID (env, jc,
|
|
"createStatement",
|
|
"()Ljava/sql/Statement;");
|
|
JException(env);
|
|
|
|
jo = (*env)->CallObjectMethod (env, ji->connection, jm);
|
|
JException(env);
|
|
ji->statement = (*env)->NewGlobalRef(env, jo);
|
|
JException(env);
|
|
jc = (*env)->GetObjectClass(env, ji->statement);
|
|
|
|
ji->executeUpdate = (*env)->GetMethodID (env, jc,
|
|
"executeUpdate",
|
|
"(Ljava/lang/String;)I");
|
|
JException(env);
|
|
|
|
ji->executeQuery = (*env)->GetMethodID (env, jc,
|
|
"executeQuery",
|
|
"(Ljava/lang/String;)Ljava/sql/ResultSet;");
|
|
JException(env);
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
|
|
jc = (*env)->GetObjectClass(env, ji->connection);
|
|
jm = (*env)->GetMethodID (env, jc,
|
|
"getMetaData", "()Ljava/sql/DatabaseMetaData;");
|
|
JException(env);
|
|
jo = (*env)->CallObjectMethod (env, ji->connection, jm);
|
|
JException(env);
|
|
jc = (*env)->GetObjectClass(env, jo);
|
|
jm = (*env)->GetMethodID (env, jc,
|
|
"supportsBatchUpdates", "()Z");
|
|
JException(env);
|
|
if ((*env)->CallBooleanMethod (env, jo, jm) == JNI_TRUE)
|
|
{
|
|
jc = (*env)->GetObjectClass(env, ji->statement);
|
|
ji->addBatch = (*env)->GetMethodID (env, jc,
|
|
"addBatch", "(Ljava/lang/String;)V");
|
|
JException(env);
|
|
ji->clearBatch = (*env)->GetMethodID (env, jc,
|
|
"clearBatch", "()V");
|
|
JException(env);
|
|
ji->executeBatch = (*env)->GetMethodID (env, jc,
|
|
"executeBatch", "()[I");
|
|
JException(env);
|
|
}
|
|
else
|
|
{
|
|
ji->addBatch = 0;
|
|
ji->clearBatch = 0;
|
|
ji->executeBatch = 0;
|
|
}
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
[self _backendDisconnect];
|
|
[self debug: @"Connect to '%@' using '%@' problem: %@",
|
|
[self name], [self database], localException];
|
|
return NO;
|
|
}
|
|
NS_ENDHANDLER
|
|
connected = YES;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[self debug: @"Connect to '%@' using '%@' has no class",
|
|
[self name], [self database]];
|
|
return NO;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[self debug:
|
|
@"Connect to '%@' with no user/password/database configured",
|
|
[self name]];
|
|
}
|
|
}
|
|
return connected;
|
|
}
|
|
|
|
- (void) backendDisconnect
|
|
{
|
|
if (extra != 0)
|
|
{
|
|
NS_DURING
|
|
{
|
|
if ([self isInTransaction] == YES)
|
|
{
|
|
[self rollback];
|
|
}
|
|
|
|
if ([self debugging] > 0)
|
|
{
|
|
[self debug: @"Disconnecting client %@", [self clientName]];
|
|
}
|
|
|
|
[self _backendDisconnect];
|
|
|
|
if ([self debugging] > 0)
|
|
{
|
|
[self debug: @"Disconnected client %@", [self clientName]];
|
|
}
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
[self _backendDisconnect];
|
|
[self debug: @"Error disconnecting from database (%@): %@",
|
|
[self clientName], localException];
|
|
}
|
|
NS_ENDHANDLER
|
|
connected = NO;
|
|
}
|
|
}
|
|
|
|
- (NSInteger) backendExecute: (NSArray*)info
|
|
{
|
|
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
|
NSString *stmt = [info objectAtIndex: 0];
|
|
JNIEnv *env = SQLClientJNIEnv();
|
|
JInfo *ji;
|
|
|
|
if ([stmt length] == 0)
|
|
{
|
|
[arp release];
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"Statement produced null string"];
|
|
}
|
|
|
|
if ((*env)->PushLocalFrame (env, 32) < 0)
|
|
{
|
|
JExceptionClear(env);
|
|
[arp release];
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"No java memory for execute"];
|
|
}
|
|
|
|
NS_DURING
|
|
{
|
|
jmethodID jm;
|
|
jobject js;
|
|
|
|
/*
|
|
* Ensure we have a working connection.
|
|
*/
|
|
if ([self connect] == NO)
|
|
{
|
|
[NSException raise: SQLException
|
|
format: @"Unable to connect to '%@' to execute statement %@",
|
|
[self name], stmt];
|
|
}
|
|
|
|
ji = (JInfo*)extra;
|
|
|
|
if ([info count] > 1)
|
|
{
|
|
unsigned i;
|
|
jclass jc;
|
|
|
|
stmt = [stmt stringByReplacingString: @"'?'''?'" withString: @"?"];
|
|
|
|
js = (*env)->CallObjectMethod (env, ji->connection, ji->prepare,
|
|
JStringFromNSString(env, stmt));
|
|
JException(env);
|
|
|
|
jc = (*env)->GetObjectClass(env, js);
|
|
JException(env);
|
|
jm = (*env)->GetMethodID (env, jc, "setBytes", "(I[B)V");
|
|
JException(env);
|
|
|
|
for (i = 1; i < [info count]; i++)
|
|
{
|
|
(*env)->CallIntMethod (env, js, jm, i,
|
|
ByteArrayFromNSData(env, [info objectAtIndex: i]));
|
|
JException(env);
|
|
}
|
|
|
|
jm = (*env)->GetMethodID (env, jc, "executeUpdate", "()I");
|
|
JException(env);
|
|
(*env)->CallIntMethod (env, js, jm);
|
|
}
|
|
else
|
|
{
|
|
(*env)->CallIntMethod (env, ji->statement,
|
|
ji->executeUpdate, JStringFromNSString(env, stmt));
|
|
}
|
|
JException(env);
|
|
if (_inTransaction == NO)
|
|
{
|
|
// Not in a transaction ... commit at once.
|
|
(*env)->CallVoidMethod (env, ji->connection, ji->commit);
|
|
JException (env);
|
|
}
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
if (connected == YES)
|
|
{
|
|
if (_inTransaction == NO)
|
|
{
|
|
ji = (JInfo*)extra;
|
|
// Not in a transaction ... rollback to clear error state
|
|
(*env)->CallVoidMethod (env, ji->connection, ji->rollback);
|
|
JExceptionClear (env);
|
|
}
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
if ([self debugging] > 0)
|
|
{
|
|
[self debug: @"Error executing statement:\n%@\n%@",
|
|
stmt, localException];
|
|
}
|
|
}
|
|
[localException retain];
|
|
[arp release];
|
|
[localException autorelease];
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER
|
|
[arp release];
|
|
return -1;
|
|
}
|
|
|
|
- (NSMutableArray*) backendQuery: (NSString*)stmt
|
|
recordType: (id)rType
|
|
listType: (id)lType
|
|
{
|
|
NSMutableArray *records = nil;
|
|
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
|
JNIEnv *env = SQLClientJNIEnv();
|
|
JInfo *ji;
|
|
|
|
if ([stmt length] == 0)
|
|
{
|
|
[arp release];
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"Statement produced null string"];
|
|
}
|
|
|
|
if ((*env)->PushLocalFrame (env, 32) < 0)
|
|
{
|
|
JExceptionClear(env);
|
|
[arp release];
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"No java memory for query"];
|
|
}
|
|
|
|
NS_DURING
|
|
{
|
|
int fieldCount;
|
|
jclass resultClass;
|
|
jobject result;
|
|
jclass metaDatlType;
|
|
jobject metaData;
|
|
jmethodID jm;
|
|
|
|
/*
|
|
* Ensure we have a working connection.
|
|
*/
|
|
if ([self connect] == NO)
|
|
{
|
|
[NSException raise: SQLException
|
|
format: @"Unable to connect to '%@' to run query %@",
|
|
[self name], stmt];
|
|
}
|
|
|
|
ji = (JInfo*)extra;
|
|
|
|
result = (*env)->CallObjectMethod (env, ji->statement, ji->executeQuery,
|
|
JStringFromNSString(env, stmt));
|
|
JException (env);
|
|
resultClass = (*env)->GetObjectClass(env, result);
|
|
JException (env);
|
|
jm = (*env)->GetMethodID (env, resultClass,
|
|
"getMetaData", "()Ljava/sql/ResultSetMetaData;");
|
|
JException (env);
|
|
metaData = (*env)->CallObjectMethod (env, result, jm);
|
|
JException (env);
|
|
metaDatlType = (*env)->GetObjectClass(env, metaData);
|
|
JException (env);
|
|
jm = (*env)->GetMethodID (env, metaDatlType,
|
|
"getColumnCount", "()I");
|
|
JException (env);
|
|
fieldCount = (*env)->CallIntMethod (env, metaData, jm);
|
|
JException (env);
|
|
|
|
if (fieldCount > 0)
|
|
{
|
|
NSString *keys[fieldCount];
|
|
int types[fieldCount];
|
|
unsigned i;
|
|
jmethodID next;
|
|
jmethodID wasNull;
|
|
jmethodID getBinaryStream;
|
|
jmethodID getBoolean;
|
|
jmethodID getBytes;
|
|
jmethodID getString;
|
|
|
|
/* Get the names of each field
|
|
*/
|
|
jm = (*env)->GetMethodID (env, metaDatlType,
|
|
"getColumnName", "(I)Ljava/lang/String;");
|
|
JException (env);
|
|
for (i = 0; i < fieldCount; i++)
|
|
{
|
|
jstring js = (*env)->CallObjectMethod (env, metaData, jm, i+1);
|
|
|
|
JException (env);
|
|
keys[i] = NSStringFromJString (env, js);
|
|
}
|
|
|
|
/* Get the types of each field.
|
|
* We treat most as strings.
|
|
*/
|
|
jm = (*env)->GetMethodID (env, metaDatlType,
|
|
"getColumnType", "(I)I");
|
|
JException (env);
|
|
for (i = 0; i < fieldCount; i++)
|
|
{
|
|
int v = (*env)->CallIntMethod (env, metaData, jm, i+1);
|
|
|
|
if (v == JDBCDATE || v == JDBCTIME || v == JDBCTIMESTAMP)
|
|
{
|
|
types[i] = JDBCTIMESTAMP;
|
|
}
|
|
else if (v == JDBCBOOLEAN)
|
|
{
|
|
types[i] = JDBCBOOLEAN;
|
|
}
|
|
else if (v == JDBCBLOB || v == JDBCBINARY || v == JDBCVARBINARY
|
|
|| v == JDBCLONGVARBINARY)
|
|
{
|
|
types[i] = JDBCBLOB;
|
|
}
|
|
else
|
|
{
|
|
types[i] = JDBCVARCHAR;
|
|
}
|
|
}
|
|
|
|
/* Iterate through the result set
|
|
*/
|
|
wasNull = (*env)->GetMethodID (env, resultClass,
|
|
"wasNull", "()Z");
|
|
JException (env);
|
|
getBinaryStream = (*env)->GetMethodID (env, resultClass,
|
|
"getBinaryStream", "(I)Ljava/io/InputStream;");
|
|
JException (env);
|
|
getBoolean = (*env)->GetMethodID (env, resultClass,
|
|
"getBoolean", "(I)Z");
|
|
JException (env);
|
|
getBytes = (*env)->GetMethodID (env, resultClass,
|
|
"getBytes", "(I)[B");
|
|
JException (env);
|
|
getString = (*env)->GetMethodID (env, resultClass,
|
|
"getString", "(I)Ljava/lang/String;");
|
|
JException (env);
|
|
|
|
next = (*env)->GetMethodID (env, resultClass,
|
|
"next", "()Z");
|
|
JException (env);
|
|
records = [[lType alloc] initWithCapacity: 2];
|
|
while ((*env)->CallBooleanMethod (env, result, next) == JNI_TRUE)
|
|
{
|
|
SQLRecord *record;
|
|
id values[fieldCount];
|
|
int j;
|
|
|
|
if ((*env)->PushLocalFrame (env, fieldCount * 2) < 0)
|
|
{
|
|
JExceptionClear(env);
|
|
[arp release];
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"No java memory for query"];
|
|
}
|
|
NS_DURING
|
|
{
|
|
for (j = 0; j < fieldCount; j++)
|
|
{
|
|
id v = null;
|
|
|
|
if (types[j] == JDBCBOOLEAN)
|
|
{
|
|
BOOL b = NO;
|
|
|
|
if ((*env)->CallBooleanMethod (env, result,
|
|
getBoolean, j+1) == JNI_TRUE)
|
|
{
|
|
b = YES;
|
|
}
|
|
JException (env);
|
|
if ((*env)->CallBooleanMethod (env, result,
|
|
wasNull) == JNI_FALSE)
|
|
{
|
|
if (b == YES)
|
|
{
|
|
v = @"Y";
|
|
}
|
|
else
|
|
{
|
|
v = @"N";
|
|
}
|
|
}
|
|
JException (env);
|
|
}
|
|
else if (types[j] == JDBCTIMESTAMP)
|
|
{
|
|
jobject jo;
|
|
|
|
jo = (*env)->CallObjectMethod (env, result,
|
|
getString, j+1);
|
|
JException (env);
|
|
if ((*env)->CallBooleanMethod (env, result,
|
|
wasNull) == JNI_FALSE)
|
|
{
|
|
v = NSStringFromJString(env, jo);
|
|
v = NSDateFromNSString(v);
|
|
}
|
|
JException (env);
|
|
}
|
|
else if (types[j] == JDBCBLOB)
|
|
{
|
|
jbyteArray jo;
|
|
|
|
jo = (*env)->CallObjectMethod (env, result,
|
|
getBytes, j+1);
|
|
JException (env);
|
|
if ((*env)->CallBooleanMethod (env, result,
|
|
wasNull) == JNI_FALSE)
|
|
{
|
|
v = NSDataFromByteArray(env, jo);
|
|
}
|
|
JException (env);
|
|
}
|
|
else
|
|
{
|
|
jobject jo;
|
|
|
|
jo = (*env)->CallObjectMethod (env, result,
|
|
getString, j+1);
|
|
JException (env);
|
|
if ((*env)->CallBooleanMethod (env, result,
|
|
wasNull) == JNI_FALSE)
|
|
{
|
|
v = NSStringFromJString(env, jo);
|
|
}
|
|
JException (env);
|
|
}
|
|
values[j] = v;
|
|
}
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER
|
|
record = [rType newWithValues: values
|
|
keys: keys
|
|
count: fieldCount];
|
|
[records addObject: record];
|
|
[record release];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
records = [[lType alloc] initWithCapacity: 0];
|
|
}
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSString *n = [localException name];
|
|
|
|
if (connected == YES)
|
|
{
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
if ([n isEqual: SQLConnectionException] == YES)
|
|
{
|
|
[self disconnect];
|
|
}
|
|
if ([self debugging] > 0)
|
|
{
|
|
[self debug: @"Error executing statement:\n%@\n%@",
|
|
stmt, localException];
|
|
}
|
|
}
|
|
[records release];
|
|
records = nil;
|
|
[localException retain];
|
|
[arp release];
|
|
[localException autorelease];
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER
|
|
[arp release];
|
|
return [records autorelease];
|
|
}
|
|
|
|
- (SQLTransaction*) batch: (BOOL)stopOnFailure
|
|
{
|
|
_JDBCTransaction *transaction;
|
|
|
|
transaction = (_JDBCTransaction*)NSAllocateObject([_JDBCTransaction class], 0,
|
|
NSDefaultMallocZone());
|
|
|
|
transaction->_owner = [self retain];
|
|
transaction->_info = [NSMutableArray new];
|
|
transaction->_batch = YES;
|
|
transaction->_stop = stopOnFailure;
|
|
return [(SQLTransaction*)transaction autorelease];
|
|
}
|
|
|
|
- (void) begin
|
|
{
|
|
[lock lock];
|
|
if (_inTransaction == NO)
|
|
{
|
|
_inTransaction = YES;
|
|
// Leave us locked so the transaction can't be interfered with
|
|
}
|
|
else
|
|
{
|
|
[lock unlock];
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"begin used inside transaction"];
|
|
}
|
|
}
|
|
|
|
- (void) commit
|
|
{
|
|
[lock lock];
|
|
if (_inTransaction == NO)
|
|
{
|
|
[lock unlock];
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"commit used outside transaction"];
|
|
}
|
|
NS_DURING
|
|
{
|
|
JNIEnv *env = SQLClientJNIEnv();
|
|
JInfo *ji = (JInfo*)extra;
|
|
|
|
(*env)->CallVoidMethod (env, ji->connection, ji->commit);
|
|
JException(env);
|
|
_inTransaction = NO;
|
|
[lock unlock]; // Locked at start of -commit
|
|
[lock unlock]; // Locked by -begin
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
_inTransaction = NO;
|
|
[lock unlock]; // Locked at start of -commit
|
|
[lock unlock]; // Locked by -begin
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[self disconnect];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSString*) quoteString: (NSString *)s
|
|
{
|
|
static NSCharacterSet *special = nil;
|
|
NSMutableString *m;
|
|
NSRange r;
|
|
unsigned l;
|
|
|
|
if (special == nil)
|
|
{
|
|
NSString *stemp;
|
|
|
|
/*
|
|
* NB. length of C string is 3, so we include a nul character as a
|
|
* special.
|
|
*/
|
|
stemp = [[NSString alloc] initWithBytes: "'\\"
|
|
length: 3
|
|
encoding: NSASCIIStringEncoding];
|
|
special = [NSCharacterSet characterSetWithCharactersInString: stemp];
|
|
[stemp release];
|
|
[special retain];
|
|
}
|
|
|
|
/*
|
|
* Step through string removing nul characters
|
|
* and escaping quote characters as required.
|
|
*/
|
|
m = [[s mutableCopy] autorelease];
|
|
l = [m length];
|
|
r = NSMakeRange(0, l);
|
|
r = [m rangeOfCharacterFromSet: special options: NSLiteralSearch range: r];
|
|
while (r.length > 0)
|
|
{
|
|
unichar c = [m characterAtIndex: r.location];
|
|
|
|
if (c == 0)
|
|
{
|
|
r.length = 1;
|
|
[m replaceCharactersInRange: r withString: @""];
|
|
l--;
|
|
}
|
|
else if (c == '\\')
|
|
{
|
|
r.length = 0;
|
|
[m replaceCharactersInRange: r withString: @"\\"];
|
|
l++;
|
|
r.location += 2;
|
|
}
|
|
else
|
|
{
|
|
r.length = 0;
|
|
[m replaceCharactersInRange: r withString: @"'"];
|
|
l++;
|
|
r.location += 2;
|
|
}
|
|
r = NSMakeRange(r.location, l - r.location);
|
|
r = [m rangeOfCharacterFromSet: special
|
|
options: NSLiteralSearch
|
|
range: r];
|
|
}
|
|
|
|
/* Add quoting around it. */
|
|
[m replaceCharactersInRange: NSMakeRange(0, 0) withString: @"'"];
|
|
[m appendString: @"'"];
|
|
return m;
|
|
}
|
|
|
|
- (void) rollback
|
|
{
|
|
[lock lock];
|
|
if (_inTransaction == YES)
|
|
{
|
|
_inTransaction = NO;
|
|
NS_DURING
|
|
{
|
|
JNIEnv *env = SQLClientJNIEnv();
|
|
JInfo *ji = (JInfo*)extra;
|
|
|
|
(*env)->CallVoidMethod (env, ji->connection, ji->rollback);
|
|
JException(env);
|
|
[lock unlock]; // Locked at start of -rollback
|
|
[lock unlock]; // Locked by -begin
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
[lock unlock]; // Locked at start of -rollback
|
|
[lock unlock]; // Locked by -begin
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
}
|
|
|
|
- (SQLTransaction*) transaction
|
|
{
|
|
_JDBCTransaction *transaction;
|
|
|
|
transaction = (_JDBCTransaction*)NSAllocateObject([_JDBCTransaction class], 0,
|
|
NSDefaultMallocZone());
|
|
|
|
transaction->_owner = [self retain];
|
|
transaction->_info = [NSMutableArray new];
|
|
return [(SQLTransaction*)transaction autorelease];
|
|
}
|
|
@end
|
|
|
|
@implementation _JDBCTransaction
|
|
|
|
- (BOOL) _batchable: (NSArray*)a
|
|
{
|
|
unsigned c = [a count];
|
|
unsigned i;
|
|
|
|
for (i = 0; i < c; i++)
|
|
{
|
|
if ([[a objectAtIndex: i] count] > 1)
|
|
{
|
|
return NO;
|
|
}
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (void) _merge: (NSMutableArray*)a
|
|
{
|
|
unsigned c = [_info count];
|
|
unsigned i;
|
|
|
|
for (i = 0; i < c; i++)
|
|
{
|
|
id o = [_info objectAtIndex: i];
|
|
|
|
if ([o isKindOfClass: [NSArray class]] == YES)
|
|
{
|
|
[a addObject: o];
|
|
}
|
|
else
|
|
{
|
|
[(_JDBCTransaction*)o _merge: a];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) execute
|
|
{
|
|
if (_count > 0)
|
|
{
|
|
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
|
SQLClientPool *pool;
|
|
SQLClient *db;
|
|
BOOL wrapped = NO;
|
|
BOOL batched = NO;
|
|
JNIEnv *env;
|
|
JInfo *ji;
|
|
|
|
env = SQLClientJNIEnv();
|
|
if ((*env)->PushLocalFrame (env, 32) < 0)
|
|
{
|
|
JExceptionClear(env);
|
|
[arp release];
|
|
[NSException raise: NSInternalInconsistencyException
|
|
format: @"No java memory for execute"];
|
|
}
|
|
|
|
if ([_owner isKindOfClass: [SQLClientPool class]])
|
|
{
|
|
pool = (SQLClientPool*)_owner;
|
|
db = [pool provideClient];
|
|
}
|
|
else
|
|
{
|
|
pool = nil;
|
|
db = _owner;
|
|
}
|
|
|
|
ji = [(SQLClientJDBC*)db _backendExtra];
|
|
|
|
NS_DURING
|
|
{
|
|
NSMutableArray *statements;
|
|
unsigned numberOfStatements;
|
|
unsigned statement;
|
|
NSTimeInterval _duration;
|
|
NSTimeInterval start = 0.0;
|
|
|
|
/*
|
|
* Ensure we have a working connection.
|
|
*/
|
|
if ([db connect] == NO)
|
|
{
|
|
[NSException raise: SQLException
|
|
format: @"Unable to connect to '%@' to execute transaction %@",
|
|
[_owner name], self];
|
|
}
|
|
|
|
_duration = [db durationLogging];
|
|
statements = [NSMutableArray arrayWithCapacity: 100];
|
|
[self _merge: statements];
|
|
numberOfStatements = [statements count];
|
|
|
|
if (_duration >= 0)
|
|
{
|
|
start = GSTickerTimeNow();
|
|
}
|
|
|
|
if ([db isInTransaction] == NO)
|
|
{
|
|
wrapped = YES;
|
|
}
|
|
|
|
if (numberOfStatements > 1 && ji->addBatch != 0
|
|
&& [self _batchable: statements] == YES)
|
|
{
|
|
jintArray ja;
|
|
jint *array;
|
|
int status = 0;
|
|
|
|
for (statement = 0; statement < numberOfStatements; statement++)
|
|
{
|
|
NSString *stmt = [statements objectAtIndex: statement];
|
|
jobject js;
|
|
|
|
js = (*env)->CallObjectMethod(env, ji->statement,
|
|
ji->addBatch, JStringFromNSString(env, stmt));
|
|
JException(env);
|
|
batched = YES;
|
|
}
|
|
ja = (*env)->CallObjectMethod(env, ji->statement,
|
|
ji->executeBatch);
|
|
JException(env);
|
|
array = (*env)->GetIntArrayElements(env, ja, 0);
|
|
for (statement = 0; statement < numberOfStatements; statement++)
|
|
{
|
|
status = array[statement];
|
|
if (status < 0 && status != -2)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
(*env)->ReleaseIntArrayElements(env, ja, array, 0);
|
|
|
|
batched = NO;
|
|
(*env)->CallVoidMethod(env, ji->statement, ji->clearBatch);
|
|
JException(env);
|
|
|
|
if (statement != numberOfStatements)
|
|
{
|
|
[NSException raise: NSGenericException
|
|
format: @"Statement %d error %d in batch with %@",
|
|
statement, status, [statements objectAtIndex: statement]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Not batchable ... execute as a transaction without
|
|
* batching :-(
|
|
*/
|
|
for (statement = 0; statement < numberOfStatements; statement++)
|
|
{
|
|
NSArray *info = [statements objectAtIndex: statement];
|
|
NSString *stmt = [info objectAtIndex: 0];
|
|
unsigned c = [info count];
|
|
jmethodID jm;
|
|
jobject js;
|
|
|
|
if (c == 1)
|
|
{
|
|
(*env)->CallIntMethod (env, ji->statement,
|
|
ji->executeUpdate, JStringFromNSString(env, stmt));
|
|
}
|
|
else
|
|
{
|
|
unsigned i;
|
|
jclass jc;
|
|
|
|
stmt = [stmt stringByReplacingString: @"'?'''?'"
|
|
withString: @"?"];
|
|
|
|
js = (*env)->CallObjectMethod
|
|
(env, ji->connection, ji->prepare,
|
|
JStringFromNSString(env, stmt));
|
|
JException(env);
|
|
|
|
jc = (*env)->GetObjectClass(env, js);
|
|
JException(env);
|
|
jm = (*env)->GetMethodID (env, jc, "setBytes", "(I[B)V");
|
|
JException(env);
|
|
|
|
/* Get data arguments for statement.
|
|
*/
|
|
for (i = 1; i < c; i++)
|
|
{
|
|
NSData *data;
|
|
|
|
data = [info objectAtIndex: i];
|
|
(*env)->CallIntMethod (env, js, jm, i,
|
|
ByteArrayFromNSData(env, data));
|
|
JException(env);
|
|
}
|
|
|
|
jm = (*env)->GetMethodID(env, jc, "executeUpdate", "()I");
|
|
JException(env);
|
|
(*env)->CallIntMethod (env, js, jm);
|
|
}
|
|
JException(env);
|
|
}
|
|
}
|
|
|
|
if (wrapped == YES)
|
|
{
|
|
wrapped = NO;
|
|
(*env)->CallVoidMethod (env, ji->connection, ji->commit);
|
|
JException(env);
|
|
}
|
|
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
|
|
db->_lastOperation = GSTickerTimeNow();
|
|
if (_duration >= 0)
|
|
{
|
|
NSTimeInterval d;
|
|
|
|
d = db->_lastOperation - start;
|
|
if (d >= _duration)
|
|
{
|
|
[db debug: @"Duration %g for transaction %@",
|
|
d, statements];
|
|
}
|
|
}
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
if (wrapped == YES)
|
|
{
|
|
(*env)->CallVoidMethod (env, ji->connection, ji->rollback);
|
|
JException(env);
|
|
}
|
|
if (batched == YES)
|
|
{
|
|
(*env)->CallVoidMethod(env, ji->statement, ji->clearBatch);
|
|
JException(env);
|
|
}
|
|
(*env)->PopLocalFrame (env, NULL);
|
|
[localException raise];
|
|
}
|
|
NS_ENDHANDLER
|
|
|
|
if (nil != pool)
|
|
{
|
|
[pool swallowClient: db];
|
|
}
|
|
|
|
[arp release];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
|