2004-04-26 15:13:27 +00:00
|
|
|
|
/* -*-objc-*- */
|
|
|
|
|
|
|
|
|
|
/** Implementation of SQLClient 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 Library General Public
|
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
|
version 2 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
|
|
|
|
|
Library General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Library 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/NSArray.h>
|
|
|
|
|
#include <Foundation/NSString.h>
|
|
|
|
|
#include <Foundation/NSData.h>
|
|
|
|
|
#include <Foundation/NSDate.h>
|
|
|
|
|
#include <Foundation/NSCalendarDate.h>
|
2005-05-25 14:06:19 +00:00
|
|
|
|
#include <Foundation/NSCharacterSet.h>
|
2004-04-26 15:13:27 +00:00
|
|
|
|
#include <Foundation/NSException.h>
|
|
|
|
|
#include <Foundation/NSProcessInfo.h>
|
|
|
|
|
#include <Foundation/NSNotification.h>
|
|
|
|
|
#include <Foundation/NSUserDefaults.h>
|
2005-09-22 09:04:08 +00:00
|
|
|
|
#include <Foundation/NSHashTable.h>
|
2004-04-26 15:13:27 +00:00
|
|
|
|
#include <Foundation/NSMapTable.h>
|
|
|
|
|
#include <Foundation/NSBundle.h>
|
|
|
|
|
#include <Foundation/NSLock.h>
|
|
|
|
|
#include <Foundation/NSAutoreleasePool.h>
|
|
|
|
|
#include <Foundation/NSValue.h>
|
|
|
|
|
#include <Foundation/NSNull.h>
|
|
|
|
|
#include <Foundation/NSDebug.h>
|
|
|
|
|
#include <Foundation/NSPathUtilities.h>
|
2005-03-02 10:00:24 +00:00
|
|
|
|
#include <Foundation/NSSet.h>
|
2005-09-26 11:22:35 +00:00
|
|
|
|
#include <Foundation/NSTimer.h>
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
#include <GNUstepBase/GSLock.h>
|
2005-11-15 14:16:37 +00:00
|
|
|
|
#include <Performance/GSCache.h>
|
|
|
|
|
#include <Performance/GSTicker.h>
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
#include "SQLClient.h"
|
|
|
|
|
|
2006-08-26 12:49:59 +00:00
|
|
|
|
static NSNull *null = nil;
|
|
|
|
|
static Class NSStringClass = 0;
|
|
|
|
|
static Class NSArrayClass = 0;
|
|
|
|
|
static Class NSDateClass = 0;
|
|
|
|
|
static Class NSSetClass = 0;
|
|
|
|
|
|
|
|
|
|
@interface SQLClientPool : NSObject
|
|
|
|
|
{
|
|
|
|
|
unsigned pool;
|
|
|
|
|
NSString *name;
|
|
|
|
|
NSString *serv;
|
|
|
|
|
NSString *user;
|
|
|
|
|
NSString *pass;
|
|
|
|
|
NSString *path;
|
|
|
|
|
NSHashTable *idle;
|
|
|
|
|
NSHashTable *used;
|
|
|
|
|
}
|
|
|
|
|
- (BOOL) isSingle;
|
|
|
|
|
- (BOOL) makeIdle: (SQLClient*)c;
|
|
|
|
|
- (BOOL) makeUsed: (SQLClient*)c;
|
|
|
|
|
- (void) setConfiguration: (NSDictionary*)o;
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLClientPool
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
if (idle != 0)
|
|
|
|
|
{
|
|
|
|
|
NSFreeHashTable(idle);
|
|
|
|
|
idle = 0;
|
|
|
|
|
}
|
|
|
|
|
if (used != 0)
|
|
|
|
|
{
|
|
|
|
|
NSFreeHashTable(used);
|
|
|
|
|
used = 0;
|
|
|
|
|
}
|
|
|
|
|
DESTROY(name);
|
|
|
|
|
DESTROY(serv);
|
|
|
|
|
DESTROY(user);
|
|
|
|
|
DESTROY(pass);
|
|
|
|
|
DESTROY(path);
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithConfiguration: (NSDictionary*)config
|
|
|
|
|
name: (NSString*)reference
|
|
|
|
|
{
|
|
|
|
|
name = [reference copy];
|
|
|
|
|
idle = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 16);
|
|
|
|
|
used = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 16);
|
|
|
|
|
[self setConfiguration: config];
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) isSingle
|
|
|
|
|
{
|
|
|
|
|
if (pool == 1)
|
|
|
|
|
{
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) makeIdle: (SQLClient*)c
|
|
|
|
|
{
|
|
|
|
|
if (NSHashGet(idle, (void*)c) == (void*)c)
|
|
|
|
|
{
|
|
|
|
|
return YES; // Already idle
|
|
|
|
|
}
|
|
|
|
|
if (NSHashGet(used, (void*)c) == (void*)c)
|
|
|
|
|
{
|
|
|
|
|
NSHashRemove(used, (void*)c);
|
|
|
|
|
}
|
|
|
|
|
if (NSCountHashTable(idle) + NSCountHashTable(used) < pool)
|
|
|
|
|
{
|
|
|
|
|
NSHashInsert(idle, (void*)c);
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) makeUsed: (SQLClient*)c
|
|
|
|
|
{
|
|
|
|
|
if (NSHashGet(used, (void*)c) == (void*)c)
|
|
|
|
|
{
|
|
|
|
|
return YES; // Already used
|
|
|
|
|
}
|
|
|
|
|
if (NSHashGet(idle, (void*)c) == (void*)c)
|
|
|
|
|
{
|
|
|
|
|
NSHashRemove(idle, (void*)c);
|
|
|
|
|
}
|
|
|
|
|
if (NSCountHashTable(idle) + NSCountHashTable(used) < pool)
|
|
|
|
|
{
|
|
|
|
|
NSHashInsert(used, (void*)c);
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setConfiguration: (NSDictionary*)o
|
|
|
|
|
{
|
|
|
|
|
NSDictionary *d;
|
|
|
|
|
NSString *s;
|
|
|
|
|
BOOL change = NO;
|
|
|
|
|
int capacity;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get dictionary containing config info for this client by name.
|
|
|
|
|
*/
|
|
|
|
|
d = [o objectForKey: @"SQLClientReferences"];
|
|
|
|
|
if ([d isKindOfClass: [NSDictionary class]] == NO)
|
|
|
|
|
{
|
|
|
|
|
d = nil;
|
|
|
|
|
}
|
|
|
|
|
d = [d objectForKey: name];
|
|
|
|
|
if ([d isKindOfClass: [NSDictionary class]] == NO)
|
|
|
|
|
{
|
|
|
|
|
d = nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s = [d objectForKey: @"ServerType"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = @"Postgres";
|
|
|
|
|
}
|
|
|
|
|
if (s != serv && [s isEqual: serv] == NO)
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(serv, s);
|
|
|
|
|
change = YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s = [d objectForKey: @"Database"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = [o objectForKey: @"Database"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = nil;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (s != path && [s isEqual: path] == NO)
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(path, s);
|
|
|
|
|
change = YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s = [d objectForKey: @"User"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = [o objectForKey: @"User"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = @"";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (s != user && [s isEqual: user] == NO)
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(user, s);
|
|
|
|
|
change = YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s = [d objectForKey: @"Password"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = [o objectForKey: @"Password"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = @"";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (s != pass && [s isEqual: pass] == NO)
|
|
|
|
|
{
|
|
|
|
|
ASSIGNCOPY(pass, s);
|
|
|
|
|
change = YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s = [d objectForKey: @"Password"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = @"1";
|
|
|
|
|
}
|
|
|
|
|
capacity = [s intValue];
|
|
|
|
|
if (capacity < 1) capacity = 1;
|
|
|
|
|
if (capacity > 100) capacity = 100;
|
|
|
|
|
|
|
|
|
|
if (change == YES)
|
|
|
|
|
{
|
|
|
|
|
NSResetHashTable(idle);
|
|
|
|
|
NSResetHashTable(used);
|
|
|
|
|
}
|
|
|
|
|
if (pool > capacity)
|
|
|
|
|
{
|
|
|
|
|
unsigned ic = NSCountHashTable(idle);
|
|
|
|
|
unsigned uc = NSCountHashTable(used);
|
|
|
|
|
|
|
|
|
|
if (ic + uc > capacity)
|
|
|
|
|
{
|
|
|
|
|
NSHashEnumerator e = NSEnumerateHashTable(idle);
|
|
|
|
|
void *c;
|
|
|
|
|
|
|
|
|
|
while (ic + uc > capacity
|
|
|
|
|
&& (c = NSNextHashEnumeratorItem(&e)) != nil)
|
|
|
|
|
{
|
|
|
|
|
NSHashRemove(idle, c);
|
|
|
|
|
ic--;
|
|
|
|
|
}
|
|
|
|
|
NSEndHashTableEnumeration(&e);
|
|
|
|
|
if (uc > capacity)
|
|
|
|
|
{
|
|
|
|
|
NSHashEnumerator e = NSEnumerateHashTable(used);
|
|
|
|
|
void *c;
|
|
|
|
|
|
|
|
|
|
while (uc > capacity
|
|
|
|
|
&& (c = NSNextHashEnumeratorItem(&e)) != nil)
|
|
|
|
|
{
|
|
|
|
|
NSHashRemove(used, c);
|
|
|
|
|
uc--;
|
|
|
|
|
}
|
|
|
|
|
NSEndHashTableEnumeration(&e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pool = capacity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
typedef struct {
|
|
|
|
|
@defs(SQLTransaction);
|
|
|
|
|
} *TDefs;
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
@class _SQLRecord;
|
|
|
|
|
static Class rClass = 0;
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@implementation SQLRecord
|
|
|
|
|
+ (id) allocWithZone: (NSZone*)aZone
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Illegal attempt to allocate an SQLRecord");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2004-08-22 09:34:18 +00:00
|
|
|
|
+ (void) initialize
|
|
|
|
|
{
|
2005-11-14 20:37:33 +00:00
|
|
|
|
GSTickerTimeNow();
|
2004-08-22 09:34:18 +00:00
|
|
|
|
if (null == nil)
|
|
|
|
|
{
|
|
|
|
|
null = [NSNull new];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
rClass = [_SQLRecord class];
|
2004-08-22 09:34:18 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
+ (id) newWithValues: (id*)v keys: (NSString**)k count: (unsigned int)c
|
|
|
|
|
{
|
|
|
|
|
return [rClass newWithValues: v keys: k count: c];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSArray*) allKeys
|
|
|
|
|
{
|
|
|
|
|
unsigned count = [self count];
|
|
|
|
|
id buf[count];
|
|
|
|
|
|
|
|
|
|
while (count-- > 0)
|
|
|
|
|
{
|
|
|
|
|
buf[count] = [self keyAtIndex: count];
|
|
|
|
|
}
|
|
|
|
|
return [NSArray arrayWithObjects: buf count: count];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) copyWithZone: (NSZone*)z
|
|
|
|
|
{
|
|
|
|
|
return RETAIN(self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (unsigned int) count
|
|
|
|
|
{
|
|
|
|
|
[self subclassResponsibility: _cmd];
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableDictionary*) dictionary
|
|
|
|
|
{
|
|
|
|
|
unsigned count = [self count];
|
|
|
|
|
id keys[count];
|
|
|
|
|
id vals[count];
|
|
|
|
|
|
|
|
|
|
[self getKeys: keys];
|
|
|
|
|
[self getObjects: vals];
|
|
|
|
|
return [NSMutableDictionary dictionaryWithObjects: vals
|
|
|
|
|
forKeys: keys
|
|
|
|
|
count: count];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) getKeys: (id*)buf
|
|
|
|
|
{
|
|
|
|
|
unsigned i = [self count];
|
|
|
|
|
|
|
|
|
|
while (i-- > 0)
|
|
|
|
|
{
|
|
|
|
|
buf[i] = [self keyAtIndex: i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) getObjects: (id*)buf
|
|
|
|
|
{
|
|
|
|
|
unsigned i = [self count];
|
|
|
|
|
|
|
|
|
|
while (i-- > 0)
|
|
|
|
|
{
|
|
|
|
|
buf[i] = [self objectAtIndex: i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Illegal attempt to -init an SQLRecord");
|
|
|
|
|
DESTROY(self);
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) keyAtIndex: (unsigned int)pos
|
|
|
|
|
{
|
|
|
|
|
return [self subclassResponsibility: _cmd];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) objectAtIndex: (unsigned int)pos
|
|
|
|
|
{
|
|
|
|
|
return [self subclassResponsibility: _cmd];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) objectForKey: (NSString*)key
|
|
|
|
|
{
|
|
|
|
|
unsigned count = [self count];
|
|
|
|
|
unsigned pos;
|
|
|
|
|
id keys[count];
|
|
|
|
|
|
|
|
|
|
[self getKeys: keys];
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
if ([key isEqualToString: keys[pos]] == YES)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (pos == count)
|
|
|
|
|
{
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
if ([key caseInsensitiveCompare: keys[pos]] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pos == count)
|
|
|
|
|
{
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return [self objectAtIndex: pos];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) replaceObjectAtIndex: (unsigned)index withObject: (id)anObject
|
|
|
|
|
{
|
|
|
|
|
[self subclassResponsibility: _cmd];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setObject: (id)anObject forKey: (NSString*)aKey
|
|
|
|
|
{
|
|
|
|
|
unsigned count = [self count];
|
|
|
|
|
unsigned pos;
|
|
|
|
|
id keys[count];
|
|
|
|
|
|
|
|
|
|
if (anObject == nil)
|
|
|
|
|
{
|
|
|
|
|
anObject = null;
|
|
|
|
|
}
|
|
|
|
|
[self getKeys: keys];
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
if ([aKey isEqualToString: keys[pos]] == YES)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (pos == count)
|
|
|
|
|
{
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
if ([aKey caseInsensitiveCompare: keys[pos]] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pos == count)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Bad key (%@) in -setObject:forKey:", aKey];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[self replaceObjectAtIndex: pos withObject: anObject];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (unsigned) sizeInBytes: (NSMutableSet*)exclude
|
|
|
|
|
{
|
|
|
|
|
if ([exclude member: self] != nil)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
unsigned size = [super sizeInBytes: exclude];
|
|
|
|
|
unsigned pos;
|
|
|
|
|
unsigned count = [self count];
|
|
|
|
|
id vals[count];
|
|
|
|
|
|
|
|
|
|
[self getObjects: vals];
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
size += [vals[pos] sizeInBytes: exclude];
|
|
|
|
|
}
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLRecord (KVC)
|
|
|
|
|
- (void) setValue: (id)aValue forKey: (NSString*)aKey
|
|
|
|
|
{
|
|
|
|
|
[self setObject: aValue forKey: aKey];
|
|
|
|
|
}
|
|
|
|
|
- (id) valueForKey: (NSString*)aKey
|
|
|
|
|
{
|
|
|
|
|
id v = [self objectForKey: aKey];
|
|
|
|
|
|
|
|
|
|
if (v == nil)
|
|
|
|
|
{
|
|
|
|
|
v = [super valueForKey: aKey];
|
|
|
|
|
}
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@interface _SQLRecord : SQLRecord
|
|
|
|
|
{
|
|
|
|
|
unsigned count;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation _SQLRecord
|
|
|
|
|
|
2004-08-22 09:34:18 +00:00
|
|
|
|
+ (id) newWithValues: (id*)v keys: (NSString**)k count: (unsigned int)c
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
2007-03-08 17:12:55 +00:00
|
|
|
|
_SQLRecord *r;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
unsigned pos;
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
r = (_SQLRecord*)NSAllocateObject(self,
|
|
|
|
|
c*2*sizeof(id), NSDefaultMallocZone());
|
2004-04-26 15:13:27 +00:00
|
|
|
|
r->count = c;
|
|
|
|
|
ptr = ((void*)&(r->count)) + sizeof(r->count);
|
|
|
|
|
for (pos = 0; pos < c; pos++)
|
|
|
|
|
{
|
2004-08-22 09:34:18 +00:00
|
|
|
|
if (v[pos] == nil)
|
|
|
|
|
{
|
|
|
|
|
ptr[pos] = RETAIN(null);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ptr[pos] = RETAIN(v[pos]);
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
ptr[pos + c] = RETAIN(k[pos]);
|
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSArray*) allKeys
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
return [NSArray arrayWithObjects: &ptr[count] count: count];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) copyWithZone: (NSZone*)z
|
|
|
|
|
{
|
|
|
|
|
return RETAIN(self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (unsigned int) count
|
|
|
|
|
{
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
unsigned pos;
|
|
|
|
|
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
DESTROY(ptr[pos]);
|
|
|
|
|
DESTROY(ptr[count + pos]);
|
|
|
|
|
}
|
2005-08-03 05:39:29 +00:00
|
|
|
|
[super dealloc];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-08-22 09:34:18 +00:00
|
|
|
|
- (NSMutableDictionary*) dictionary
|
|
|
|
|
{
|
|
|
|
|
NSMutableDictionary *d;
|
|
|
|
|
unsigned pos;
|
|
|
|
|
id *ptr;
|
|
|
|
|
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
d = [NSMutableDictionary dictionaryWithCapacity: count];
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
[d setObject: ptr[pos] forKey: [ptr[pos + count] lowercaseString]];
|
|
|
|
|
}
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
- (void) getKeys: (id*)buf
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
unsigned pos;
|
|
|
|
|
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
ptr += count; // Step past objects to keys.
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
buf[pos] = ptr[pos];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2005-09-30 13:12:27 +00:00
|
|
|
|
- (void) getObjects: (id*)buf
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
unsigned pos;
|
|
|
|
|
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
buf[pos] = ptr[pos];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Illegal attempt to -init an SQLRecord");
|
|
|
|
|
DESTROY(self);
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
- (id) keyAtIndex: (unsigned int)pos
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
|
|
|
|
|
if (pos >= count)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSRangeException
|
|
|
|
|
format: @"Array index too large"];
|
|
|
|
|
}
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
ptr += count;
|
|
|
|
|
return ptr[pos];
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (id) objectAtIndex: (unsigned int)pos
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
|
|
|
|
|
if (pos >= count)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSRangeException
|
|
|
|
|
format: @"Array index too large"];
|
|
|
|
|
}
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
return ptr[pos];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) objectForKey: (NSString*)key
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
unsigned int pos;
|
|
|
|
|
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
if ([key isEqualToString: ptr[pos + count]] == YES)
|
|
|
|
|
{
|
|
|
|
|
return ptr[pos];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
if ([key caseInsensitiveCompare: ptr[pos + count]] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
return ptr[pos];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
2005-09-20 12:57:48 +00:00
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
- (void) replaceObjectAtIndex: (unsigned)index withObject: (id)anObject
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
|
|
|
|
|
if (index >= count)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSRangeException
|
|
|
|
|
format: @"Array index too large"];
|
|
|
|
|
}
|
|
|
|
|
if (anObject == nil)
|
|
|
|
|
{
|
|
|
|
|
anObject = null;
|
|
|
|
|
}
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
ptr += index;
|
|
|
|
|
ASSIGN(*ptr, anObject);
|
|
|
|
|
}
|
|
|
|
|
|
2005-09-20 12:57:48 +00:00
|
|
|
|
- (void) setObject: (id)anObject forKey: (NSString*)aKey
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
unsigned int pos;
|
|
|
|
|
|
|
|
|
|
if (anObject == nil)
|
|
|
|
|
{
|
|
|
|
|
anObject = null;
|
|
|
|
|
}
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
if ([aKey isEqualToString: ptr[pos + count]] == YES)
|
|
|
|
|
{
|
|
|
|
|
ASSIGN(ptr[pos], anObject);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
if ([aKey caseInsensitiveCompare: ptr[pos + count]] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
ASSIGN(ptr[pos], anObject);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Bad key (%@) in -setObject:forKey:", aKey];
|
|
|
|
|
}
|
2005-09-22 08:42:37 +00:00
|
|
|
|
|
|
|
|
|
- (unsigned) sizeInBytes: (NSMutableSet*)exclude
|
|
|
|
|
{
|
|
|
|
|
if ([exclude member: self] != nil)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
unsigned size = [super sizeInBytes: exclude];
|
|
|
|
|
unsigned pos;
|
|
|
|
|
id *ptr;
|
|
|
|
|
|
|
|
|
|
ptr = ((void*)&count) + sizeof(count);
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
size += [ptr[pos] sizeInBytes: exclude];
|
|
|
|
|
}
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Exception raised when an error with the remote database server occurs.
|
|
|
|
|
*/
|
|
|
|
|
NSString *SQLException = @"SQLException";
|
|
|
|
|
/**
|
|
|
|
|
* Exception for when a connection to the server is lost.
|
|
|
|
|
*/
|
|
|
|
|
NSString *SQLConnectionException = @"SQLConnectionException";
|
|
|
|
|
/**
|
|
|
|
|
* Exception for when a query is supposed to return data and doesn't.
|
|
|
|
|
*/
|
|
|
|
|
NSString *SQLEmptyException = @"SQLEmptyException";
|
|
|
|
|
/**
|
|
|
|
|
* Exception for when an insert/update would break the uniqueness of a
|
|
|
|
|
* field or index.
|
|
|
|
|
*/
|
|
|
|
|
NSString *SQLUniqueException = @"SQLUniqueException";
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient (Logging)
|
|
|
|
|
|
2004-10-26 15:54:29 +00:00
|
|
|
|
static unsigned int classDebugging = 0;
|
|
|
|
|
static NSTimeInterval classDuration = -1;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
+ (unsigned int) debugging
|
|
|
|
|
{
|
|
|
|
|
return classDebugging;
|
|
|
|
|
}
|
|
|
|
|
|
2004-05-07 08:16:16 +00:00
|
|
|
|
+ (NSTimeInterval) durationLogging
|
|
|
|
|
{
|
|
|
|
|
return classDuration;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
+ (void) setDebugging: (unsigned int)level
|
|
|
|
|
{
|
|
|
|
|
classDebugging = level;
|
|
|
|
|
}
|
|
|
|
|
|
2004-05-07 08:16:16 +00:00
|
|
|
|
+ (void) setDurationLogging: (NSTimeInterval)threshold
|
|
|
|
|
{
|
|
|
|
|
classDuration = threshold;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (void) debug: (NSString*)fmt, ...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
NSLogv(fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (unsigned int) debugging
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _debugging;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSTimeInterval) durationLogging
|
|
|
|
|
{
|
|
|
|
|
return _duration;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setDebugging: (unsigned int)level
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
_debugging = level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setDurationLogging: (NSTimeInterval)threshold
|
|
|
|
|
{
|
|
|
|
|
_duration = threshold;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Container for all instances.
|
|
|
|
|
*/
|
2005-09-27 06:35:05 +00:00
|
|
|
|
static NSMapTable *clientsMap = 0;
|
|
|
|
|
static NSRecursiveLock *clientsMapLock = nil;
|
2004-09-17 14:54:56 +00:00
|
|
|
|
static NSString *beginString = @"begin";
|
2004-04-26 15:13:27 +00:00
|
|
|
|
static NSArray *beginStatement = nil;
|
2004-09-17 14:54:56 +00:00
|
|
|
|
static NSString *commitString = @"commit";
|
2004-04-26 15:13:27 +00:00
|
|
|
|
static NSArray *commitStatement = nil;
|
2004-09-17 14:54:56 +00:00
|
|
|
|
static NSString *rollbackString = @"rollback";
|
2004-04-26 15:13:27 +00:00
|
|
|
|
static NSArray *rollbackStatement = nil;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@interface SQLClient (Private)
|
|
|
|
|
- (void) _configure: (NSNotification*)n;
|
|
|
|
|
- (NSArray*) _prepare: (NSString*)stmt args: (va_list)args;
|
|
|
|
|
- (NSArray*) _substitute: (NSString*)str with: (NSDictionary*)vals;
|
2005-09-27 06:35:05 +00:00
|
|
|
|
+ (void) _tick: (NSTimer*)t;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient
|
|
|
|
|
|
|
|
|
|
static unsigned int maxConnections = 8;
|
|
|
|
|
|
|
|
|
|
+ (NSArray*) allClients
|
|
|
|
|
{
|
|
|
|
|
NSArray *a;
|
|
|
|
|
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock lock];
|
|
|
|
|
a = NSAllMapTableValues(clientsMap);
|
|
|
|
|
[clientsMapLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
return a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (SQLClient*) clientWithConfiguration: (NSDictionary*)config
|
|
|
|
|
name: (NSString*)reference
|
|
|
|
|
{
|
|
|
|
|
SQLClient *o;
|
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([reference isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2004-06-29 17:11:10 +00:00
|
|
|
|
if (config == nil)
|
|
|
|
|
{
|
|
|
|
|
reference = [[NSUserDefaults standardUserDefaults] objectForKey:
|
|
|
|
|
@"SQLClientName"];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
reference = [config objectForKey: @"SQLClientName"];
|
|
|
|
|
}
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([reference isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
reference = @"Database";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
o = [self existingClient: reference];
|
|
|
|
|
if (o == nil)
|
|
|
|
|
{
|
|
|
|
|
o = [[SQLClient alloc] initWithConfiguration: config name: reference];
|
|
|
|
|
AUTORELEASE(o);
|
|
|
|
|
}
|
|
|
|
|
return o;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (SQLClient*) existingClient: (NSString*)reference
|
|
|
|
|
{
|
|
|
|
|
SQLClient *existing;
|
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([reference isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
reference = [[NSUserDefaults standardUserDefaults] stringForKey:
|
|
|
|
|
@"SQLClientName"];
|
|
|
|
|
if (reference == nil)
|
|
|
|
|
{
|
|
|
|
|
reference = @"Database";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock lock];
|
|
|
|
|
existing = (SQLClient*)NSMapGet(clientsMap, reference);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
AUTORELEASE(RETAIN(existing));
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
return existing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (void) initialize
|
|
|
|
|
{
|
2005-11-14 20:37:33 +00:00
|
|
|
|
GSTickerTimeNow();
|
2007-03-08 17:12:55 +00:00
|
|
|
|
[SQLRecord class]; // Force initialisation
|
2005-09-27 06:35:05 +00:00
|
|
|
|
if (clientsMap == 0)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2005-09-27 06:35:05 +00:00
|
|
|
|
clientsMap = NSCreateMapTable(NSObjectMapKeyCallBacks,
|
2004-04-26 15:13:27 +00:00
|
|
|
|
NSNonRetainedObjectMapValueCallBacks, 0);
|
2005-09-27 06:35:05 +00:00
|
|
|
|
clientsMapLock = [GSLazyRecursiveLock new];
|
2004-09-17 14:54:56 +00:00
|
|
|
|
beginStatement = RETAIN([NSArray arrayWithObject: beginString]);
|
|
|
|
|
commitStatement = RETAIN([NSArray arrayWithObject: commitString]);
|
|
|
|
|
rollbackStatement = RETAIN([NSArray arrayWithObject: rollbackString]);
|
2004-10-07 09:30:14 +00:00
|
|
|
|
NSStringClass = [NSString class];
|
2006-02-18 17:10:56 +00:00
|
|
|
|
NSArrayClass = [NSArray class];
|
2006-02-22 11:15:16 +00:00
|
|
|
|
NSSetClass = [NSSet class];
|
2005-09-26 11:22:35 +00:00
|
|
|
|
[NSTimer scheduledTimerWithTimeInterval: 1.0
|
|
|
|
|
target: self
|
|
|
|
|
selector: @selector(_tick:)
|
|
|
|
|
userInfo: 0
|
|
|
|
|
repeats: YES];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (unsigned int) maxConnections
|
|
|
|
|
{
|
|
|
|
|
return maxConnections;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (void) purgeConnections: (NSDate*)since
|
|
|
|
|
{
|
|
|
|
|
NSMapEnumerator e;
|
|
|
|
|
NSString *n;
|
2004-10-07 09:30:14 +00:00
|
|
|
|
SQLClient *o;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
unsigned int connectionCount = 0;
|
2004-10-07 09:30:14 +00:00
|
|
|
|
NSTimeInterval t = [since timeIntervalSinceReferenceDate];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock lock];
|
|
|
|
|
e = NSEnumerateMapTable(clientsMap);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
while (NSNextMapEnumeratorPair(&e, (void**)&n, (void**)&o) != 0)
|
|
|
|
|
{
|
|
|
|
|
if (since != nil)
|
|
|
|
|
{
|
2004-10-07 09:30:14 +00:00
|
|
|
|
NSTimeInterval when = o->_lastOperation;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if (when < t)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
[o disconnect];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ([o connected] == YES)
|
|
|
|
|
{
|
|
|
|
|
connectionCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NSEndMapTableEnumeration(&e);
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
while (connectionCount >= maxConnections)
|
|
|
|
|
{
|
|
|
|
|
SQLClient *other = nil;
|
2004-10-07 09:30:14 +00:00
|
|
|
|
NSTimeInterval oldest = 0.0;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
connectionCount = 0;
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock lock];
|
|
|
|
|
e = NSEnumerateMapTable(clientsMap);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
while (NSNextMapEnumeratorPair(&e, (void**)&n, (void**)&o))
|
|
|
|
|
{
|
|
|
|
|
if ([o connected] == YES)
|
|
|
|
|
{
|
2004-10-07 09:30:14 +00:00
|
|
|
|
NSTimeInterval when = o->_lastOperation;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
connectionCount++;
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if (oldest == 0.0 || when < oldest)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
oldest = when;
|
|
|
|
|
other = o;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NSEndMapTableEnumeration(&e);
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
connectionCount--;
|
|
|
|
|
if ([other debugging] > 0)
|
|
|
|
|
{
|
|
|
|
|
[other debug:
|
|
|
|
|
@"Force disconnect of '%@' because pool size (%d) reached",
|
|
|
|
|
other, maxConnections];
|
|
|
|
|
}
|
|
|
|
|
[other disconnect];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (void) setMaxConnections: (unsigned int)c
|
|
|
|
|
{
|
|
|
|
|
if (c > 0)
|
|
|
|
|
{
|
|
|
|
|
maxConnections = c;
|
|
|
|
|
[self purgeConnections: nil];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) begin
|
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if (_inTransaction == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
_inTransaction = YES;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
[self simpleExecute: beginStatement];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
2004-05-07 08:16:16 +00:00
|
|
|
|
_inTransaction = NO;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"begin used inside transaction"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
- (NSString*) buildQuery: (NSString*)stmt, ...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
NSString *sql = nil;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* First check validity and concatenate parts of the query.
|
|
|
|
|
*/
|
|
|
|
|
va_start (ap, stmt);
|
|
|
|
|
sql = [[self _prepare: stmt args: ap] objectAtIndex: 0];
|
|
|
|
|
va_end (ap);
|
|
|
|
|
|
|
|
|
|
return sql;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) buildQuery: (NSString*)stmt with: (NSDictionary*)values
|
|
|
|
|
{
|
|
|
|
|
NSString *sql = nil;
|
|
|
|
|
|
|
|
|
|
sql = [[self _substitute: stmt with: values] objectAtIndex: 0];
|
|
|
|
|
|
|
|
|
|
return sql;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (NSString*) clientName
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _client;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) commit
|
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if (_inTransaction == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"commit used outside transaction"];
|
|
|
|
|
}
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
[self simpleExecute: commitStatement];
|
|
|
|
|
_inTransaction = NO;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[lock unlock]; // Locked at start of -commit
|
2004-09-17 14:54:56 +00:00
|
|
|
|
[lock unlock]; // Locked by -begin
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
_inTransaction = NO;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[lock unlock]; // Locked at start of -commit
|
2004-09-17 14:54:56 +00:00
|
|
|
|
[lock unlock]; // Locked by -begin
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) connect
|
|
|
|
|
{
|
|
|
|
|
if (connected == NO)
|
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
|
|
|
|
if (connected == NO)
|
|
|
|
|
{
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
[self backendConnect];
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
[lock unlock];
|
|
|
|
|
}
|
|
|
|
|
return connected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) connected
|
|
|
|
|
{
|
|
|
|
|
return connected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) database
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _database;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
NSNotificationCenter *nc;
|
|
|
|
|
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if (_name != nil)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock lock];
|
|
|
|
|
NSMapRemove(clientsMap, (void*)_name);
|
|
|
|
|
[clientsMapLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2006-09-10 13:37:23 +00:00
|
|
|
|
nc = [NSNotificationCenter defaultCenter];
|
|
|
|
|
[nc removeObserver: self];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[self disconnect];
|
|
|
|
|
DESTROY(lock);
|
2004-05-07 08:16:16 +00:00
|
|
|
|
DESTROY(_client);
|
|
|
|
|
DESTROY(_database);
|
|
|
|
|
DESTROY(_password);
|
|
|
|
|
DESTROY(_user);
|
|
|
|
|
DESTROY(_name);
|
2004-09-17 14:54:56 +00:00
|
|
|
|
DESTROY(_statements);
|
2005-03-02 10:00:24 +00:00
|
|
|
|
DESTROY(_cache);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) description
|
|
|
|
|
{
|
|
|
|
|
NSMutableString *s = AUTORELEASE([NSMutableString new]);
|
|
|
|
|
|
2004-05-07 08:16:16 +00:00
|
|
|
|
[s appendFormat: @"Database - %@\n", [self clientName]];
|
|
|
|
|
[s appendFormat: @" Name - %@\n", [self name]];
|
|
|
|
|
[s appendFormat: @" DBase - %@\n", [self database]];
|
|
|
|
|
[s appendFormat: @" DB User - %@\n", [self user]];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[s appendFormat: @" Password - %@\n",
|
2004-05-07 08:16:16 +00:00
|
|
|
|
[self password] == nil ? @"unknown" : @"known"];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[s appendFormat: @" Connected - %@\n", connected ? @"yes" : @"no"];
|
2006-03-29 06:46:37 +00:00
|
|
|
|
[s appendFormat: @" Transaction - %@\n", _inTransaction ? @"yes" : @"no"];
|
|
|
|
|
if (_cache == nil)
|
|
|
|
|
{
|
|
|
|
|
[s appendString: @"\n"];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[s appendFormat: @" Cache - %@\n", _cache];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) disconnect
|
|
|
|
|
{
|
|
|
|
|
if (connected == YES)
|
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
|
|
|
|
if (connected == YES)
|
|
|
|
|
{
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
[self backendDisconnect];
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
[lock unlock];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) execute: (NSString*)stmt, ...
|
|
|
|
|
{
|
|
|
|
|
NSArray *info;
|
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
|
|
|
|
info = [self _prepare: stmt args: ap];
|
|
|
|
|
va_end (ap);
|
|
|
|
|
[self simpleExecute: info];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) execute: (NSString*)stmt with: (NSDictionary*)values
|
|
|
|
|
{
|
|
|
|
|
NSArray *info;
|
|
|
|
|
|
|
|
|
|
info = [self _substitute: stmt with: values];
|
|
|
|
|
[self simpleExecute: info];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
return [self initWithConfiguration: nil name: nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithConfiguration: (NSDictionary*)config
|
|
|
|
|
{
|
|
|
|
|
return [self initWithConfiguration: config name: nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithConfiguration: (NSDictionary*)config
|
|
|
|
|
name: (NSString*)reference
|
|
|
|
|
{
|
|
|
|
|
NSNotification *n;
|
2005-07-07 21:11:04 +00:00
|
|
|
|
NSDictionary *conf = config;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
id existing;
|
|
|
|
|
|
2004-06-29 17:11:10 +00:00
|
|
|
|
if (conf == nil)
|
|
|
|
|
{
|
2005-07-07 21:11:04 +00:00
|
|
|
|
// Pretend the defaults object is a dictionary.
|
|
|
|
|
conf = (NSDictionary*)[NSUserDefaults standardUserDefaults];
|
2004-06-29 17:11:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([reference isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2004-06-29 17:11:10 +00:00
|
|
|
|
reference = [conf objectForKey: @"SQLClientName"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([reference isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2004-06-29 17:11:10 +00:00
|
|
|
|
reference = [conf objectForKey: @"Database"];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock lock];
|
|
|
|
|
existing = (SQLClient*)NSMapGet(clientsMap, reference);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
if (existing == nil)
|
|
|
|
|
{
|
|
|
|
|
lock = [GSLazyRecursiveLock new]; // Ensure thread-safety.
|
|
|
|
|
[self setDebugging: [[self class] debugging]];
|
2004-05-07 08:16:16 +00:00
|
|
|
|
[self setDurationLogging: [[self class] durationLogging]];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[self setName: reference]; // Set name and store in cache.
|
2004-09-17 14:54:56 +00:00
|
|
|
|
_statements = [NSMutableArray new];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2004-06-29 17:11:10 +00:00
|
|
|
|
if ([conf isKindOfClass: [NSUserDefaults class]] == YES)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
NSNotificationCenter *nc;
|
|
|
|
|
|
|
|
|
|
nc = [NSNotificationCenter defaultCenter];
|
|
|
|
|
[nc addObserver: self
|
|
|
|
|
selector: @selector(_configure:)
|
|
|
|
|
name: NSUserDefaultsDidChangeNotification
|
2004-06-29 17:11:10 +00:00
|
|
|
|
object: conf];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2004-06-29 17:11:10 +00:00
|
|
|
|
n = [NSNotification
|
|
|
|
|
notificationWithName: NSUserDefaultsDidChangeNotification
|
|
|
|
|
object: conf
|
|
|
|
|
userInfo: nil];
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[self _configure: n]; // Actually set up the configuration.
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RELEASE(self);
|
|
|
|
|
self = RETAIN(existing);
|
|
|
|
|
}
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) isInTransaction
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _inTransaction;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSDate*) lastOperation
|
|
|
|
|
{
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if (_lastOperation > 0.0)
|
|
|
|
|
{
|
|
|
|
|
return [NSDate dateWithTimeIntervalSinceReferenceDate: _lastOperation];
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) name
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _name;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) password
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _password;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) query: (NSString*)stmt, ...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
NSMutableArray *result = nil;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* First check validity and concatenate parts of the query.
|
|
|
|
|
*/
|
|
|
|
|
va_start (ap, stmt);
|
|
|
|
|
stmt = [[self _prepare: stmt args: ap] objectAtIndex: 0];
|
|
|
|
|
va_end (ap);
|
|
|
|
|
|
|
|
|
|
result = [self simpleQuery: stmt];
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) query: (NSString*)stmt with: (NSDictionary*)values
|
|
|
|
|
{
|
|
|
|
|
NSMutableArray *result = nil;
|
|
|
|
|
|
|
|
|
|
stmt = [[self _substitute: stmt with: values] objectAtIndex: 0];
|
|
|
|
|
|
|
|
|
|
result = [self simpleQuery: stmt];
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2004-10-08 09:29:00 +00:00
|
|
|
|
- (NSString*) quote: (id)obj
|
|
|
|
|
{
|
2004-04-26 15:13:27 +00:00
|
|
|
|
/**
|
2004-10-07 09:30:14 +00:00
|
|
|
|
* For a nil object, we return NULL.
|
2004-04-26 15:13:27 +00:00
|
|
|
|
*/
|
2005-09-28 06:20:43 +00:00
|
|
|
|
if (obj == nil || obj == null)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
return @"NULL";
|
|
|
|
|
}
|
2004-10-07 09:30:14 +00:00
|
|
|
|
else if ([obj isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2004-10-07 09:30:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* For a number, we simply convert directly to a string.
|
|
|
|
|
*/
|
|
|
|
|
if ([obj isKindOfClass: [NSNumber class]] == YES)
|
|
|
|
|
{
|
|
|
|
|
return [obj description];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* For a date, we convert to the text format used by the database,
|
|
|
|
|
* and add leading and trailing quotes.
|
|
|
|
|
*/
|
|
|
|
|
if ([obj isKindOfClass: NSDateClass] == YES)
|
|
|
|
|
{
|
|
|
|
|
return [obj descriptionWithCalendarFormat:
|
|
|
|
|
@"'%Y-%m-%d %H:%M:%S.%F %z'" timeZone: nil locale: nil];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* For a data object, we don't quote ... the other parts of the code
|
|
|
|
|
* need to know they have an NSData object and pass it on unchanged
|
|
|
|
|
* to the -backendExecute: method.
|
|
|
|
|
*/
|
|
|
|
|
if ([obj isKindOfClass: [NSData class]] == YES)
|
|
|
|
|
{
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
|
2005-09-28 06:20:43 +00:00
|
|
|
|
/**
|
|
|
|
|
* Just in case an NSNull subclass has been created by someone.
|
|
|
|
|
* The normal NSNull instance should have been handled earlier.
|
|
|
|
|
*/
|
|
|
|
|
if ([obj isKindOfClass: [NSNull class]] == YES)
|
|
|
|
|
{
|
|
|
|
|
return @"NULL";
|
|
|
|
|
}
|
|
|
|
|
|
2006-02-18 17:10:56 +00:00
|
|
|
|
/**
|
2006-02-22 11:15:16 +00:00
|
|
|
|
* For an NSArray or NSSet, we produce a bracketed list of the
|
2006-02-18 17:10:56 +00:00
|
|
|
|
* (quoted) objects in the array.
|
|
|
|
|
*/
|
2006-02-22 11:15:16 +00:00
|
|
|
|
if ([obj isKindOfClass: NSArrayClass] == YES ||
|
|
|
|
|
[obj isKindOfClass: NSSetClass] == YES)
|
2006-02-18 17:10:56 +00:00
|
|
|
|
{
|
|
|
|
|
NSMutableString *ms = [NSMutableString stringWithCapacity: 100];
|
2006-02-22 11:15:16 +00:00
|
|
|
|
NSEnumerator *enumerator = [obj objectEnumerator];
|
|
|
|
|
id value = [enumerator nextObject];
|
2006-02-18 17:10:56 +00:00
|
|
|
|
|
|
|
|
|
[ms appendString: @"("];
|
2006-02-22 11:15:16 +00:00
|
|
|
|
if (value != nil)
|
2006-02-18 17:10:56 +00:00
|
|
|
|
{
|
2006-02-22 11:15:16 +00:00
|
|
|
|
[ms appendString: [self quote: value]];
|
|
|
|
|
}
|
|
|
|
|
while ((value = [enumerator nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
[ms appendString: @","];
|
|
|
|
|
[ms appendString: [self quote: value]];
|
2006-02-18 17:10:56 +00:00
|
|
|
|
}
|
|
|
|
|
[ms appendString: @")"];
|
|
|
|
|
return ms;
|
|
|
|
|
}
|
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
/**
|
|
|
|
|
* For any other type of data, we just produce a quoted string
|
|
|
|
|
* representation of the objects description.
|
|
|
|
|
*/
|
|
|
|
|
obj = [obj description];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
/* Get a string description of the object. */
|
2006-05-25 11:34:03 +00:00
|
|
|
|
obj = [self quoteString: obj];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
|
2004-10-08 09:29:00 +00:00
|
|
|
|
- (NSString*) quotef: (NSString*)fmt, ...
|
|
|
|
|
{
|
2006-05-25 11:34:03 +00:00
|
|
|
|
va_list ap;
|
|
|
|
|
NSString *str;
|
|
|
|
|
NSString *quoted;
|
2004-10-08 09:29:00 +00:00
|
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
2006-05-25 11:34:03 +00:00
|
|
|
|
str = [[NSString allocWithZone: NSDefaultMallocZone()]
|
2004-10-08 09:29:00 +00:00
|
|
|
|
initWithFormat: fmt arguments: ap];
|
|
|
|
|
va_end(ap);
|
|
|
|
|
|
2006-05-25 11:34:03 +00:00
|
|
|
|
quoted = [self quoteString: str];
|
|
|
|
|
RELEASE(str);
|
|
|
|
|
return quoted;
|
2004-10-08 09:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (NSString*) quoteCString: (const char *)s
|
|
|
|
|
{
|
2006-05-25 11:34:03 +00:00
|
|
|
|
NSString *str;
|
|
|
|
|
NSString *quoted;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2006-05-25 11:34:03 +00:00
|
|
|
|
if (s == 0)
|
|
|
|
|
{
|
|
|
|
|
s = "";
|
|
|
|
|
}
|
|
|
|
|
str = [[NSString alloc] initWithCString: s];
|
|
|
|
|
quoted = [self quoteString: str];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
RELEASE(str);
|
2006-05-25 11:34:03 +00:00
|
|
|
|
return quoted;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) quoteChar: (char)c
|
|
|
|
|
{
|
2006-05-25 11:34:03 +00:00
|
|
|
|
NSString *str;
|
|
|
|
|
NSString *quoted;
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
if (c == 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Attempt to quote a nul character in -quoteChar:"];
|
|
|
|
|
}
|
2006-05-25 11:34:03 +00:00
|
|
|
|
str = [[NSString alloc] initWithFormat: @"%c", c];
|
|
|
|
|
quoted = [self quoteString: str];
|
|
|
|
|
RELEASE(str);
|
|
|
|
|
return quoted;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) quoteFloat: (float)f
|
|
|
|
|
{
|
|
|
|
|
return [NSString stringWithFormat: @"%f", f];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) quoteInteger: (int)i
|
|
|
|
|
{
|
|
|
|
|
return [NSString stringWithFormat: @"%d", i];
|
|
|
|
|
}
|
|
|
|
|
|
2006-05-25 11:34:03 +00:00
|
|
|
|
- (NSString*) quoteString: (NSString *)s
|
|
|
|
|
{
|
|
|
|
|
static NSCharacterSet *special = nil;
|
|
|
|
|
NSMutableString *m;
|
|
|
|
|
NSRange r;
|
|
|
|
|
unsigned l;
|
|
|
|
|
|
|
|
|
|
if (special == nil)
|
|
|
|
|
{
|
2006-08-03 00:14:22 +00:00
|
|
|
|
NSString *stemp;
|
2006-05-25 11:34:03 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* NB. length of C string is 2, so we include a nul character as a
|
|
|
|
|
* special.
|
|
|
|
|
*/
|
2006-08-03 00:14:22 +00:00
|
|
|
|
stemp = [[NSString alloc] initWithBytes: "'"
|
|
|
|
|
length: 2
|
|
|
|
|
encoding: NSASCIIStringEncoding];
|
|
|
|
|
special = [NSCharacterSet characterSetWithCharactersInString: stemp];
|
|
|
|
|
RELEASE(stemp);
|
2006-05-25 11:34:03 +00:00
|
|
|
|
RETAIN(special);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Step through string removing nul characters
|
|
|
|
|
* and escaping quote characters as required.
|
|
|
|
|
*/
|
|
|
|
|
m = AUTORELEASE([s mutableCopy]);
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2006-09-10 13:37:23 +00:00
|
|
|
|
- (void) release
|
|
|
|
|
{
|
|
|
|
|
/* We lock the table while checking, to prevent
|
|
|
|
|
* another thread from grabbing this object while we are
|
|
|
|
|
* checking it.
|
|
|
|
|
* If we are going to deallocate the object, we first remove
|
|
|
|
|
* it from the table so that no other thread will find it
|
|
|
|
|
* and try to use it while it is being deallocated.
|
|
|
|
|
*/
|
|
|
|
|
[clientsMapLock lock];
|
|
|
|
|
if (NSDecrementExtraRefCountWasZero(self))
|
|
|
|
|
{
|
|
|
|
|
[self dealloc];
|
|
|
|
|
}
|
|
|
|
|
[clientsMapLock unlock];
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (void) rollback
|
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
2004-10-06 05:21:09 +00:00
|
|
|
|
if (_inTransaction == YES)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
_inTransaction = NO;
|
2004-10-06 05:21:09 +00:00
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
[self simpleExecute: rollbackStatement];
|
|
|
|
|
[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
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setDatabase: (NSString*)s
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if ([s isEqual: _database] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
if (connected == YES)
|
|
|
|
|
{
|
|
|
|
|
[self disconnect];
|
|
|
|
|
}
|
2004-05-07 08:16:16 +00:00
|
|
|
|
ASSIGNCOPY(_database, s);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setName: (NSString*)s
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if ([s isEqual: _name] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if ([s isEqual: _name] == YES)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
return;
|
|
|
|
|
}
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock lock];
|
|
|
|
|
if (NSMapGet(clientsMap, s) != 0)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
2005-09-27 06:35:05 +00:00
|
|
|
|
[clientsMapLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
if ([self debugging] > 0)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"Error attempt to re-use client name %@", s];
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (connected == YES)
|
|
|
|
|
{
|
|
|
|
|
[self disconnect];
|
|
|
|
|
}
|
|
|
|
|
RETAIN(self);
|
2005-08-03 07:03:28 +00:00
|
|
|
|
if (_name != nil)
|
|
|
|
|
{
|
2005-09-27 06:35:05 +00:00
|
|
|
|
NSMapRemove(clientsMap, (void*)_name);
|
2005-08-03 07:03:28 +00:00
|
|
|
|
}
|
2004-05-07 08:16:16 +00:00
|
|
|
|
ASSIGNCOPY(_name, s);
|
|
|
|
|
ASSIGN(_client, [[NSProcessInfo processInfo] globallyUniqueString]);
|
2005-09-27 06:35:05 +00:00
|
|
|
|
NSMapInsert(clientsMap, (void*)_name, (void*)self);
|
|
|
|
|
[clientsMapLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[lock unlock];
|
|
|
|
|
RELEASE(self);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setPassword: (NSString*)s
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if ([s isEqual: _password] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
if (connected == YES)
|
|
|
|
|
{
|
|
|
|
|
[self disconnect];
|
|
|
|
|
}
|
2004-05-07 08:16:16 +00:00
|
|
|
|
ASSIGNCOPY(_password, s);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setUser: (NSString*)s
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if ([s isEqual: _client] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
if (connected == YES)
|
|
|
|
|
{
|
|
|
|
|
[self disconnect];
|
|
|
|
|
}
|
2004-05-07 08:16:16 +00:00
|
|
|
|
ASSIGNCOPY(_user, s);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) simpleExecute: (NSArray*)info
|
|
|
|
|
{
|
2004-09-17 14:54:56 +00:00
|
|
|
|
NSString *statement;
|
2004-10-09 12:39:36 +00:00
|
|
|
|
BOOL isCommit = NO;
|
|
|
|
|
BOOL isRollback = NO;
|
2004-09-17 14:54:56 +00:00
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
[lock lock];
|
2004-09-17 14:54:56 +00:00
|
|
|
|
statement = [info objectAtIndex: 0];
|
2004-10-09 12:39:36 +00:00
|
|
|
|
if ([statement isEqualToString: commitString])
|
|
|
|
|
{
|
|
|
|
|
isCommit = YES;
|
|
|
|
|
}
|
|
|
|
|
if ([statement isEqualToString: rollbackString])
|
|
|
|
|
{
|
|
|
|
|
isRollback = YES;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2004-10-07 09:30:14 +00:00
|
|
|
|
NSTimeInterval start = 0.0;
|
2004-05-07 08:16:16 +00:00
|
|
|
|
|
|
|
|
|
if (_duration >= 0)
|
|
|
|
|
{
|
2005-11-14 20:37:33 +00:00
|
|
|
|
start = GSTickerTimeNow();
|
2004-05-07 08:16:16 +00:00
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[self backendExecute: info];
|
2005-11-14 20:37:33 +00:00
|
|
|
|
_lastOperation = GSTickerTimeNow();
|
2004-09-17 14:54:56 +00:00
|
|
|
|
[_statements addObject: statement];
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if (_duration >= 0)
|
|
|
|
|
{
|
|
|
|
|
NSTimeInterval d;
|
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
d = _lastOperation - start;
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if (d >= _duration)
|
|
|
|
|
{
|
2004-10-09 12:39:36 +00:00
|
|
|
|
if (isCommit || isRollback)
|
2004-09-17 14:54:56 +00:00
|
|
|
|
{
|
|
|
|
|
NSEnumerator *e = [_statements objectEnumerator];
|
2004-10-09 12:39:36 +00:00
|
|
|
|
if (isCommit)
|
2004-09-17 14:54:56 +00:00
|
|
|
|
{
|
|
|
|
|
[self debug:
|
|
|
|
|
@"Duration %g for transaction commit ...", d];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[self debug:
|
|
|
|
|
@"Duration %g for transaction rollback ...", d];
|
|
|
|
|
}
|
|
|
|
|
while ((statement = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @" %@;", statement];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ([self debugging] > 1)
|
2004-05-07 08:16:16 +00:00
|
|
|
|
{
|
2004-09-17 14:54:56 +00:00
|
|
|
|
/*
|
|
|
|
|
* For higher debug levels, we log data objects as well
|
|
|
|
|
* as the query string, otherwise we omit them.
|
|
|
|
|
*/
|
2004-05-07 08:16:16 +00:00
|
|
|
|
[self debug: @"Duration %g for statement %@", d, info];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"Duration %g for statement %@",
|
2004-09-17 14:54:56 +00:00
|
|
|
|
d, statement];
|
2004-05-07 08:16:16 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2004-10-09 12:39:36 +00:00
|
|
|
|
if (_inTransaction == NO || isCommit || isRollback)
|
2004-09-17 14:54:56 +00:00
|
|
|
|
{
|
|
|
|
|
[_statements removeAllObjects];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
2004-10-09 12:39:36 +00:00
|
|
|
|
if (_inTransaction == NO || isCommit || isRollback)
|
2004-09-17 14:54:56 +00:00
|
|
|
|
{
|
|
|
|
|
[_statements removeAllObjects];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[lock unlock];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) simpleQuery: (NSString*)stmt
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
|
|
|
|
return [self simpleQuery: stmt recordClass: rClass];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) simpleQuery: (NSString*)stmt recordClass: (Class)cls
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
NSMutableArray *result = nil;
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
if (cls == 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"nil class passed to simpleQuery:recordClass:"];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[lock lock];
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2004-10-07 09:30:14 +00:00
|
|
|
|
NSTimeInterval start = 0.0;
|
2004-05-07 08:16:16 +00:00
|
|
|
|
|
|
|
|
|
if (_duration >= 0)
|
|
|
|
|
{
|
2005-11-14 20:37:33 +00:00
|
|
|
|
start = GSTickerTimeNow();
|
2004-05-07 08:16:16 +00:00
|
|
|
|
}
|
2007-03-08 17:12:55 +00:00
|
|
|
|
result = [self backendQuery: stmt recordClass: cls];
|
2005-11-14 20:37:33 +00:00
|
|
|
|
_lastOperation = GSTickerTimeNow();
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if (_duration >= 0)
|
|
|
|
|
{
|
|
|
|
|
NSTimeInterval d;
|
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
d = _lastOperation - start;
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if (d >= _duration)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"Duration %g for query %@", d, stmt];
|
|
|
|
|
}
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[lock unlock];
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) user
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _user;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient (Subclass)
|
|
|
|
|
|
|
|
|
|
- (BOOL) backendConnect
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) backendDisconnect
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) backendExecute: (NSArray*)info
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) backendQuery: (NSString*)stmt
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
|
|
|
|
return [self backendQuery: stmt recordClass: rClass];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) backendQuery: (NSString*)stmt recordClass: (Class)cls
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (unsigned) copyEscapedBLOB: (NSData*)blob into: (void*)buf
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (const void*) insertBLOBs: (NSArray*)blobs
|
|
|
|
|
intoStatement: (const void*)statement
|
|
|
|
|
length: (unsigned)sLength
|
|
|
|
|
withMarker: (const void*)marker
|
|
|
|
|
length: (unsigned)mLength
|
|
|
|
|
giving: (unsigned*)result
|
|
|
|
|
{
|
|
|
|
|
unsigned count = [blobs count];
|
|
|
|
|
unsigned length = sLength;
|
|
|
|
|
|
|
|
|
|
if (count > 1)
|
|
|
|
|
{
|
|
|
|
|
unsigned i;
|
|
|
|
|
unsigned char *buf;
|
|
|
|
|
unsigned char *ptr;
|
|
|
|
|
const unsigned char *from = (const unsigned char*)statement;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Calculate length of buffer needed.
|
|
|
|
|
*/
|
|
|
|
|
for (i = 1; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
length += [self lengthOfEscapedBLOB: [blobs objectAtIndex: i]];
|
|
|
|
|
length -= mLength;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf = NSZoneMalloc(NSDefaultMallocZone(), length + 1);
|
|
|
|
|
[NSData dataWithBytesNoCopy: buf length: length + 1]; // autoreleased
|
|
|
|
|
ptr = buf;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Merge quoted data objects into statement.
|
|
|
|
|
*/
|
|
|
|
|
i = 1;
|
|
|
|
|
from = (unsigned char*)statement;
|
|
|
|
|
while (*from != 0)
|
|
|
|
|
{
|
|
|
|
|
if (*from == *(unsigned char*)marker
|
|
|
|
|
&& memcmp(from, marker, mLength) == 0)
|
|
|
|
|
{
|
|
|
|
|
NSData *d = [blobs objectAtIndex: i++];
|
|
|
|
|
|
|
|
|
|
from += mLength;
|
|
|
|
|
ptr += [self copyEscapedBLOB: d into: ptr];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
*ptr++ = *from++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*ptr = '\0';
|
|
|
|
|
statement = buf;
|
|
|
|
|
}
|
|
|
|
|
*result = length;
|
|
|
|
|
return statement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (unsigned) lengthOfEscapedBLOB: (NSData*)blob
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient (Private)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Internal method to handle configuration using the notification object.
|
|
|
|
|
* This object may be either a configuration front end or a user defaults
|
|
|
|
|
* object ... so we have to be careful that we work with both.
|
|
|
|
|
*/
|
|
|
|
|
- (void) _configure: (NSNotification*)n
|
|
|
|
|
{
|
2005-07-07 21:11:04 +00:00
|
|
|
|
NSDictionary *o = [n object];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
NSDictionary *d;
|
|
|
|
|
NSString *s;
|
|
|
|
|
Class c;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get dictionary containing config info for this client by name.
|
|
|
|
|
*/
|
|
|
|
|
d = [o objectForKey: @"SQLClientReferences"];
|
|
|
|
|
if ([d isKindOfClass: [NSDictionary class]] == NO)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"Unable to find SQLClientReferences config dictionary"];
|
|
|
|
|
d = nil;
|
|
|
|
|
}
|
2004-05-07 08:16:16 +00:00
|
|
|
|
d = [d objectForKey: _name];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
if ([d isKindOfClass: [NSDictionary class]] == NO)
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
[self debug: @"Unable to find config for client '%@'", _name];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
d = nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s = [d objectForKey: @"ServerType"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
s = @"Postgres";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c = NSClassFromString([@"SQLClient" stringByAppendingString: s]);
|
|
|
|
|
if (c == nil)
|
|
|
|
|
{
|
2006-04-05 13:31:23 +00:00
|
|
|
|
NSString *path;
|
|
|
|
|
NSBundle *bundle;
|
|
|
|
|
NSArray *paths;
|
|
|
|
|
NSMutableArray *tried;
|
|
|
|
|
unsigned count;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
2005-06-21 13:10:55 +00:00
|
|
|
|
NSLocalDomainMask, YES);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
count = [paths count];
|
2006-04-05 13:31:23 +00:00
|
|
|
|
tried = [NSMutableArray arrayWithCapacity: count];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
while (count-- > 0)
|
|
|
|
|
{
|
|
|
|
|
path = [paths objectAtIndex: count];
|
|
|
|
|
path = [path stringByAppendingPathComponent: @"Bundles"];
|
|
|
|
|
path = [path stringByAppendingPathComponent: @"SQLClient"];
|
|
|
|
|
path = [path stringByAppendingPathComponent: s];
|
|
|
|
|
path = [path stringByAppendingPathExtension: @"bundle"];
|
|
|
|
|
bundle = [NSBundle bundleWithPath: path];
|
2006-04-05 13:31:23 +00:00
|
|
|
|
if (bundle != nil)
|
2005-02-19 04:20:13 +00:00
|
|
|
|
{
|
2006-04-05 13:31:23 +00:00
|
|
|
|
[tried addObject: path];
|
|
|
|
|
if ((c = [bundle principalClass]) != nil)
|
|
|
|
|
{
|
|
|
|
|
break; // Found it.
|
|
|
|
|
}
|
2005-02-19 04:20:13 +00:00
|
|
|
|
}
|
|
|
|
|
/* Try alternative version with more libraries linked in.
|
|
|
|
|
* In some systems and situations the dynamic linker needs
|
|
|
|
|
* to haved the SQLClient, gnustep-base, and objc libraries
|
2005-07-07 21:11:04 +00:00
|
|
|
|
* explicitly linked into the bundle, but in others it
|
2005-02-19 04:20:13 +00:00
|
|
|
|
* requires them to not be linked. To handle that, we create
|
|
|
|
|
* two versions of each bundle, the seond version has _libs
|
|
|
|
|
* appended to the bundle name, and has the extra libraries linked.
|
|
|
|
|
*/
|
|
|
|
|
path = [path stringByDeletingPathExtension];
|
|
|
|
|
path = [path stringByAppendingString: @"_libs"];
|
|
|
|
|
path = [path stringByAppendingPathExtension: @"bundle"];
|
|
|
|
|
bundle = [NSBundle bundleWithPath: path];
|
2006-04-05 13:31:23 +00:00
|
|
|
|
if (bundle != nil)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2006-04-05 13:31:23 +00:00
|
|
|
|
[tried addObject: path];
|
|
|
|
|
if ((c = [bundle principalClass]) != nil)
|
|
|
|
|
{
|
|
|
|
|
break; // Found it.
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2005-02-19 04:20:13 +00:00
|
|
|
|
if (c == nil)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2006-04-05 13:31:23 +00:00
|
|
|
|
if ([tried count] == 0)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"unable to load bundle for '%@' server type"
|
|
|
|
|
@" ... failed to locate bundle in %@", s, paths];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"unable to load backend class for '%@' server type"
|
|
|
|
|
@" ... dynamic library load failed in %@", s, tried];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (c != [self class])
|
|
|
|
|
{
|
|
|
|
|
[self disconnect];
|
2005-08-03 05:39:29 +00:00
|
|
|
|
#ifdef GNUSTEP
|
2004-04-26 15:13:27 +00:00
|
|
|
|
GSDebugAllocationRemove(self->isa, self);
|
2005-08-03 05:39:29 +00:00
|
|
|
|
#endif
|
2004-04-26 15:13:27 +00:00
|
|
|
|
self->isa = c;
|
2005-08-03 05:39:29 +00:00
|
|
|
|
#ifdef GNUSTEP
|
2004-04-26 15:13:27 +00:00
|
|
|
|
GSDebugAllocationAdd(self->isa, self);
|
2005-08-03 05:39:29 +00:00
|
|
|
|
#endif
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s = [d objectForKey: @"Database"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
s = [o objectForKey: @"Database"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
s = nil;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[self setDatabase: s];
|
|
|
|
|
|
|
|
|
|
s = [d objectForKey: @"User"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
s = [o objectForKey: @"User"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
s = @"";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[self setUser: s];
|
|
|
|
|
|
|
|
|
|
s = [d objectForKey: @"Password"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
s = [o objectForKey: @"Password"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
s = @"";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[self setPassword: s];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Internal method to build an sql string by quoting any non-string objects
|
|
|
|
|
* and concatenating the resulting strings in a nil terminated list.<br />
|
|
|
|
|
* Returns an array containing the statement as the first object and
|
|
|
|
|
* any NSData objects following. The NSData objects appear in the
|
2006-05-25 11:34:03 +00:00
|
|
|
|
* statement strings as the marker sequence - <code>'?'''?'</code>
|
2004-04-26 15:13:27 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSArray*) _prepare: (NSString*)stmt args: (va_list)args
|
|
|
|
|
{
|
|
|
|
|
NSMutableArray *ma = [NSMutableArray arrayWithCapacity: 2];
|
|
|
|
|
NSString *tmp = va_arg(args, NSString*);
|
|
|
|
|
CREATE_AUTORELEASE_POOL(arp);
|
|
|
|
|
|
|
|
|
|
if (tmp != nil)
|
|
|
|
|
{
|
|
|
|
|
NSMutableString *s = [NSMutableString stringWithCapacity: 1024];
|
|
|
|
|
|
|
|
|
|
[s appendString: stmt];
|
|
|
|
|
/*
|
|
|
|
|
* Append any values from the nil terminated varargs
|
|
|
|
|
*/
|
|
|
|
|
while (tmp != nil)
|
|
|
|
|
{
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([tmp isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
if ([tmp isKindOfClass: [NSData class]] == YES)
|
|
|
|
|
{
|
|
|
|
|
[ma addObject: tmp];
|
2006-05-25 11:34:03 +00:00
|
|
|
|
[s appendString: @"'?'''?'"]; // Marker.
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[s appendString: [self quote: tmp]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[s appendString: tmp];
|
|
|
|
|
}
|
|
|
|
|
tmp = va_arg(args, NSString*);
|
|
|
|
|
}
|
|
|
|
|
stmt = s;
|
|
|
|
|
}
|
|
|
|
|
[ma insertObject: stmt atIndex: 0];
|
|
|
|
|
DESTROY(arp);
|
|
|
|
|
return ma;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Internal method to substitute values from the dictionary into
|
|
|
|
|
* a string containing markup identifying where the values should
|
|
|
|
|
* appear by name. Non-string objects in the dictionary are quoted.<br />
|
|
|
|
|
* Returns an array containing the statement as the first object and
|
|
|
|
|
* any NSData objects following. The NSData objects appear in the
|
2006-05-25 11:34:03 +00:00
|
|
|
|
* statement strings as the marker sequence - <code>'?'''?'</code>
|
2004-04-26 15:13:27 +00:00
|
|
|
|
*/
|
|
|
|
|
- (NSArray*) _substitute: (NSString*)str with: (NSDictionary*)vals
|
|
|
|
|
{
|
|
|
|
|
unsigned int l = [str length];
|
|
|
|
|
NSRange r;
|
|
|
|
|
NSMutableArray *ma = [NSMutableArray arrayWithCapacity: 2];
|
|
|
|
|
CREATE_AUTORELEASE_POOL(arp);
|
|
|
|
|
|
|
|
|
|
if (l < 2)
|
|
|
|
|
{
|
|
|
|
|
[ma addObject: str]; // Can't contain a {...} sequence
|
|
|
|
|
}
|
|
|
|
|
else if ((r = [str rangeOfString: @"{"]).length == 0)
|
|
|
|
|
{
|
|
|
|
|
[ma addObject: str]; // No '{' markup
|
|
|
|
|
}
|
|
|
|
|
else if (l - r.location < 2)
|
|
|
|
|
{
|
|
|
|
|
[ma addObject: str]; // Can't contain a {...} sequence
|
|
|
|
|
}
|
|
|
|
|
else if ([str rangeOfString: @"}" options: NSLiteralSearch
|
|
|
|
|
range: NSMakeRange(r.location, l - r.location)].length == 0
|
|
|
|
|
&& [str rangeOfString: @"{{" options: NSLiteralSearch
|
|
|
|
|
range: NSMakeRange(0, l)].length == 0)
|
|
|
|
|
{
|
|
|
|
|
[ma addObject: str]; // No closing '}' or repeated '{{'
|
|
|
|
|
}
|
|
|
|
|
else if (r.length == 0)
|
|
|
|
|
{
|
|
|
|
|
[ma addObject: str]; // Nothing to do.
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSMutableString *mtext = AUTORELEASE([str mutableCopy]);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Replace {FieldName} with the value of the field
|
|
|
|
|
*/
|
|
|
|
|
while (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
unsigned pos = r.location;
|
|
|
|
|
unsigned nxt;
|
|
|
|
|
unsigned vLength;
|
|
|
|
|
NSArray *a;
|
|
|
|
|
NSRange s;
|
|
|
|
|
NSString *v;
|
|
|
|
|
NSString *alt;
|
2005-07-07 21:11:04 +00:00
|
|
|
|
id o;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
|
|
r.length = l - pos;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the length of the string from the '{' onwards is less than two,
|
|
|
|
|
* there is nothing to do and we can end processing.
|
|
|
|
|
*/
|
|
|
|
|
if (r.length < 2)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ([mtext characterAtIndex: r.location + 1] == '{')
|
|
|
|
|
{
|
|
|
|
|
// Got '{{' ... remove one of them.
|
|
|
|
|
r.length = 1;
|
|
|
|
|
[mtext replaceCharactersInRange: r withString: @""];
|
|
|
|
|
l--;
|
|
|
|
|
r.location++;
|
|
|
|
|
r.length = l - r.location;
|
|
|
|
|
r = [mtext rangeOfString: @"{"
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: r];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r = [mtext rangeOfString: @"}"
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: r];
|
|
|
|
|
if (r.length == 0)
|
|
|
|
|
{
|
|
|
|
|
break; // No closing bracket
|
|
|
|
|
}
|
|
|
|
|
nxt = NSMaxRange(r);
|
|
|
|
|
r = NSMakeRange(pos, nxt - pos);
|
|
|
|
|
s.location = r.location + 1;
|
|
|
|
|
s.length = r.length - 2;
|
|
|
|
|
v = [mtext substringWithRange: s];
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the value contains a '?', it is actually in two parts,
|
|
|
|
|
* the first part is the field name, and the second part is
|
|
|
|
|
* an alternative text to be used if the value from the
|
|
|
|
|
* dictionary is empty.
|
|
|
|
|
*/
|
|
|
|
|
s = [v rangeOfString: @"?"];
|
|
|
|
|
if (s.length == 0)
|
|
|
|
|
{
|
|
|
|
|
alt = @""; // No alternative value.
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
alt = [v substringFromIndex: NSMaxRange(s)];
|
|
|
|
|
v = [v substringToIndex: s.location];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the value we are substituting contains dots, we split it apart.
|
|
|
|
|
* We use the value to make a reference into the dictionary we are
|
|
|
|
|
* given.
|
|
|
|
|
*/
|
|
|
|
|
a = [v componentsSeparatedByString: @"."];
|
|
|
|
|
o = vals;
|
|
|
|
|
for (i = 0; i < [a count]; i++)
|
|
|
|
|
{
|
|
|
|
|
NSString *k = [a objectAtIndex: i];
|
|
|
|
|
|
|
|
|
|
if ([k length] > 0)
|
|
|
|
|
{
|
2005-07-07 21:11:04 +00:00
|
|
|
|
o = [(NSDictionary*)o objectForKey: k];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (o == vals)
|
|
|
|
|
{
|
|
|
|
|
v = nil; // Mo match found.
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([o isKindOfClass: NSStringClass] == YES)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2005-07-07 21:11:04 +00:00
|
|
|
|
v = (NSString*)o;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if ([o isKindOfClass: [NSData class]] == YES)
|
|
|
|
|
{
|
|
|
|
|
[ma addObject: o];
|
2006-05-25 11:34:03 +00:00
|
|
|
|
v = @"'?'''?'";
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
v = [self quote: o];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ([v length] == 0)
|
|
|
|
|
{
|
|
|
|
|
v = alt;
|
2006-05-12 15:37:03 +00:00
|
|
|
|
if (v == nil)
|
|
|
|
|
{
|
|
|
|
|
v = @"";
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
vLength = [v length];
|
|
|
|
|
|
|
|
|
|
[mtext replaceCharactersInRange: r withString: v];
|
2006-05-12 15:37:03 +00:00
|
|
|
|
l += vLength; // Add length of string inserted
|
2004-04-26 15:13:27 +00:00
|
|
|
|
l -= r.length; // Remove length of string replaced
|
|
|
|
|
r.location += vLength;
|
|
|
|
|
|
|
|
|
|
if (r.location >= l)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
r.length = l - r.location;
|
|
|
|
|
r = [mtext rangeOfString: @"{"
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: r];
|
|
|
|
|
}
|
|
|
|
|
[ma insertObject: mtext atIndex: 0];
|
|
|
|
|
}
|
|
|
|
|
RELEASE(arp);
|
|
|
|
|
return ma;
|
|
|
|
|
}
|
2005-09-26 11:22:35 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Called at one second intervals to ensure that our current timestamp
|
|
|
|
|
* is reasonably accurate.
|
|
|
|
|
*/
|
2005-09-27 06:35:05 +00:00
|
|
|
|
+ (void) _tick: (NSTimer*)t
|
2005-09-26 11:22:35 +00:00
|
|
|
|
{
|
2005-11-15 14:16:37 +00:00
|
|
|
|
(void) GSTickerTimeNow();
|
2005-09-26 11:22:35 +00:00
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient(Convenience)
|
|
|
|
|
|
|
|
|
|
- (SQLRecord*) queryRecord: (NSString*)stmt, ...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
NSArray *result = nil;
|
|
|
|
|
SQLRecord *record;
|
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
|
|
|
|
stmt = [[self _prepare: stmt args: ap] objectAtIndex: 0];
|
|
|
|
|
va_end (ap);
|
|
|
|
|
|
|
|
|
|
result = [self simpleQuery: stmt];
|
|
|
|
|
|
|
|
|
|
if ([result count] > 1)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Query returns more than one record -\n%@\n", stmt];
|
|
|
|
|
}
|
|
|
|
|
record = [result lastObject];
|
|
|
|
|
if (record == nil)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: SQLEmptyException
|
|
|
|
|
format: @"Query returns no data -\n%@\n", stmt];
|
|
|
|
|
}
|
|
|
|
|
return record;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) queryString: (NSString*)stmt, ...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
NSArray *result = nil;
|
|
|
|
|
SQLRecord *record;
|
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
|
|
|
|
stmt = [[self _prepare: stmt args: ap] objectAtIndex: 0];
|
|
|
|
|
va_end (ap);
|
|
|
|
|
|
|
|
|
|
result = [self simpleQuery: stmt];
|
|
|
|
|
|
|
|
|
|
if ([result count] > 1)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Query returns more than one record -\n%@\n", stmt];
|
|
|
|
|
}
|
|
|
|
|
record = [result lastObject];
|
|
|
|
|
if (record == nil)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: SQLEmptyException
|
|
|
|
|
format: @"Query returns no data -\n%@\n", stmt];
|
|
|
|
|
}
|
|
|
|
|
if ([record count] > 1)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Query returns multiple fields -\n%@\n", stmt];
|
|
|
|
|
}
|
|
|
|
|
return [[record lastObject] description];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) singletons: (NSMutableArray*)records
|
|
|
|
|
{
|
|
|
|
|
unsigned c = [records count];
|
|
|
|
|
|
|
|
|
|
while (c-- > 0)
|
|
|
|
|
{
|
|
|
|
|
[records replaceObjectAtIndex: c
|
|
|
|
|
withObject: [[records objectAtIndex: c] lastObject]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
- (SQLTransaction*) transaction
|
|
|
|
|
{
|
|
|
|
|
TDefs transaction;
|
|
|
|
|
|
|
|
|
|
transaction = (TDefs)NSAllocateObject([SQLTransaction class], 0,
|
|
|
|
|
NSDefaultMallocZone());
|
|
|
|
|
|
|
|
|
|
transaction->_db = RETAIN(self);
|
|
|
|
|
transaction->_info = [NSMutableArray new];
|
|
|
|
|
return AUTORELEASE((SQLTransaction*)transaction);
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
2005-03-02 10:00:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@interface SQLClientCacheInfo : NSObject
|
|
|
|
|
{
|
|
|
|
|
@public
|
|
|
|
|
NSString *query;
|
|
|
|
|
NSMutableArray *result;
|
|
|
|
|
NSTimeInterval expires;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLClientCacheInfo
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
DESTROY(query);
|
|
|
|
|
DESTROY(result);
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
- (unsigned) hash
|
|
|
|
|
{
|
|
|
|
|
return [query hash];
|
|
|
|
|
}
|
2005-07-07 21:11:04 +00:00
|
|
|
|
- (BOOL) isEqual: (id)other
|
2005-03-02 10:00:24 +00:00
|
|
|
|
{
|
2005-07-07 21:11:04 +00:00
|
|
|
|
return [query isEqual: ((SQLClientCacheInfo*)other)->query];
|
2005-03-02 10:00:24 +00:00
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient (Caching)
|
2005-09-22 08:42:37 +00:00
|
|
|
|
|
2005-11-14 20:37:33 +00:00
|
|
|
|
- (GSCache*) cache
|
2005-09-22 08:42:37 +00:00
|
|
|
|
{
|
|
|
|
|
if (_cache == nil)
|
|
|
|
|
{
|
2005-11-14 20:37:33 +00:00
|
|
|
|
_cache = [GSCache new];
|
2005-09-22 08:42:37 +00:00
|
|
|
|
}
|
|
|
|
|
return _cache;
|
|
|
|
|
}
|
|
|
|
|
|
2005-03-02 10:00:24 +00:00
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
|
|
|
|
query: (NSString*)stmt,...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
|
|
|
|
stmt = [[self _prepare: stmt args: ap] objectAtIndex: 0];
|
|
|
|
|
va_end (ap);
|
|
|
|
|
|
|
|
|
|
return [self cache: seconds simpleQuery: stmt];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
|
|
|
|
query: (NSString*)stmt
|
|
|
|
|
with: (NSDictionary*)values
|
|
|
|
|
{
|
|
|
|
|
stmt = [[self _substitute: stmt with: values] objectAtIndex: 0];
|
|
|
|
|
return [self cache: seconds simpleQuery: stmt];
|
|
|
|
|
}
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
|
|
|
|
simpleQuery: (NSString*)stmt
|
|
|
|
|
{
|
|
|
|
|
return [self cache: seconds simpleQuery: stmt recordClass: rClass];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
|
|
|
|
simpleQuery: (NSString*)stmt
|
|
|
|
|
recordClass: (Class)cls
|
2005-03-02 10:00:24 +00:00
|
|
|
|
{
|
|
|
|
|
NSMutableArray *result = nil;
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
if (cls == 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"nil class passed to cache:simpleQuery:recordClass:"];
|
|
|
|
|
}
|
2005-03-02 10:00:24 +00:00
|
|
|
|
[lock lock];
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2005-11-14 20:37:33 +00:00
|
|
|
|
NSTimeInterval start = GSTickerTimeNow();
|
|
|
|
|
GSCache *c = [self cache];
|
2005-09-22 08:42:37 +00:00
|
|
|
|
id toCache = nil;
|
2005-03-02 10:00:24 +00:00
|
|
|
|
|
2005-09-22 08:42:37 +00:00
|
|
|
|
if (seconds < 0)
|
|
|
|
|
{
|
|
|
|
|
seconds = -seconds;
|
2005-03-02 10:00:24 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2005-09-22 08:42:37 +00:00
|
|
|
|
result = [c objectForKey: stmt];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result == nil)
|
|
|
|
|
{
|
2007-03-08 17:12:55 +00:00
|
|
|
|
result = toCache = [self backendQuery: stmt recordClass: cls];
|
2005-11-14 20:37:33 +00:00
|
|
|
|
_lastOperation = GSTickerTimeNow();
|
2005-03-02 10:00:24 +00:00
|
|
|
|
if (_duration >= 0)
|
|
|
|
|
{
|
|
|
|
|
NSTimeInterval d;
|
|
|
|
|
|
|
|
|
|
d = _lastOperation - start;
|
|
|
|
|
if (d >= _duration)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"Duration %g for query %@", d, stmt];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-09-27 14:07:04 +00:00
|
|
|
|
|
2005-09-22 08:42:37 +00:00
|
|
|
|
if (seconds == 0)
|
|
|
|
|
{
|
|
|
|
|
// We have been told to remove the existing cached item.
|
2005-09-27 14:07:04 +00:00
|
|
|
|
[c setObject: nil forKey: stmt lifetime: seconds];
|
2005-09-22 08:42:37 +00:00
|
|
|
|
toCache = nil;
|
|
|
|
|
}
|
2005-09-27 14:07:04 +00:00
|
|
|
|
|
|
|
|
|
if (toCache != nil)
|
|
|
|
|
{
|
|
|
|
|
// We have a newly retrieved object ... cache it.
|
|
|
|
|
[c setObject: toCache forKey: stmt lifetime: seconds];
|
|
|
|
|
}
|
2005-09-22 08:42:37 +00:00
|
|
|
|
|
|
|
|
|
if (result != nil)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Return an autoreleased copy ... not the original cached data.
|
|
|
|
|
*/
|
|
|
|
|
result = [NSMutableArray arrayWithArray: result];
|
|
|
|
|
}
|
2005-03-02 10:00:24 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[lock unlock];
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2005-11-14 20:37:33 +00:00
|
|
|
|
- (void) setCache: (GSCache*)aCache
|
2005-09-27 06:35:05 +00:00
|
|
|
|
{
|
|
|
|
|
ASSIGN(_cache, aCache);
|
|
|
|
|
}
|
2005-03-02 10:00:24 +00:00
|
|
|
|
@end
|
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
@implementation SQLTransaction
|
2005-09-28 06:35:03 +00:00
|
|
|
|
|
|
|
|
|
- (unsigned) count
|
|
|
|
|
{
|
|
|
|
|
return _count;
|
|
|
|
|
}
|
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
DESTROY(_db);
|
|
|
|
|
DESTROY(_info);
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
2005-05-22 09:21:01 +00:00
|
|
|
|
- (NSString*) description
|
|
|
|
|
{
|
|
|
|
|
return [NSString stringWithFormat: @"%@ with SQL '%@' for %@",
|
2006-05-25 11:34:03 +00:00
|
|
|
|
[super description],
|
|
|
|
|
(_count == 0 ? (id)@"" : (id)[_info objectAtIndex: 0]), _db];
|
2005-05-22 09:21:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
- (void) _addInfo: (NSArray*)info
|
|
|
|
|
{
|
|
|
|
|
if (_count == 0)
|
|
|
|
|
{
|
|
|
|
|
NSMutableString *ms = [[info objectAtIndex: 0] mutableCopy];
|
|
|
|
|
|
|
|
|
|
[_info addObjectsFromArray: info];
|
|
|
|
|
[_info replaceObjectAtIndex: 0 withObject: ms];
|
|
|
|
|
RELEASE(ms);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSMutableString *ms = [_info objectAtIndex: 0];
|
|
|
|
|
unsigned c = [info count];
|
|
|
|
|
unsigned i = 1;
|
|
|
|
|
|
|
|
|
|
[ms appendString: @";"];
|
|
|
|
|
[ms appendString: [info objectAtIndex: 0]];
|
|
|
|
|
while (i < c)
|
|
|
|
|
{
|
|
|
|
|
[_info addObject: [info objectAtIndex: i++]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_count++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) add: (NSString*)stmt,...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
|
|
|
|
[self _addInfo: [_db _prepare: stmt args: ap]];
|
|
|
|
|
va_end (ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) add: (NSString*)stmt with: (NSDictionary*)values
|
|
|
|
|
{
|
|
|
|
|
[self _addInfo: [_db _substitute: stmt with: values]];
|
|
|
|
|
}
|
|
|
|
|
|
2004-11-09 09:53:09 +00:00
|
|
|
|
- (void) append: (SQLTransaction*)other
|
|
|
|
|
{
|
|
|
|
|
if (other != nil && other->_count > 0)
|
|
|
|
|
{
|
|
|
|
|
[self _addInfo: other->_info];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-07-26 11:37:42 +00:00
|
|
|
|
- (SQLClient*) db
|
|
|
|
|
{
|
|
|
|
|
return _db;
|
|
|
|
|
}
|
|
|
|
|
|
2006-06-23 19:36:52 +00:00
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
- (void) execute
|
|
|
|
|
{
|
|
|
|
|
if (_count > 0)
|
|
|
|
|
{
|
2006-06-23 19:36:52 +00:00
|
|
|
|
BOOL wrapped = NO;
|
|
|
|
|
NSMutableString *sql = [_info objectAtIndex: 0];
|
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2004-10-06 12:10:01 +00:00
|
|
|
|
if (_count > 1 && [_db isInTransaction] == NO)
|
2004-07-26 08:56:26 +00:00
|
|
|
|
{
|
2006-06-23 19:36:52 +00:00
|
|
|
|
wrapped = YES;
|
|
|
|
|
[sql replaceCharactersInRange: NSMakeRange(0, 0)
|
|
|
|
|
withString: @"begin;"];
|
|
|
|
|
[sql replaceCharactersInRange: NSMakeRange([sql length], 0)
|
|
|
|
|
withString: @";commit"];
|
2004-07-26 08:56:26 +00:00
|
|
|
|
}
|
|
|
|
|
[_db simpleExecute: _info];
|
2006-06-23 19:36:52 +00:00
|
|
|
|
if (wrapped == YES)
|
2004-07-26 08:56:26 +00:00
|
|
|
|
{
|
2006-06-23 19:36:52 +00:00
|
|
|
|
wrapped = NO;
|
|
|
|
|
[sql replaceCharactersInRange: NSMakeRange([sql length] - 7, 7)
|
|
|
|
|
withString: @""];
|
|
|
|
|
[sql replaceCharactersInRange: NSMakeRange(0, 6)
|
|
|
|
|
withString: @""];
|
2004-07-26 08:56:26 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
2006-06-23 19:36:52 +00:00
|
|
|
|
if (wrapped == YES)
|
2004-07-26 08:56:26 +00:00
|
|
|
|
{
|
2006-06-23 19:36:52 +00:00
|
|
|
|
[sql replaceCharactersInRange: NSMakeRange([sql length] - 7, 7)
|
|
|
|
|
withString: @""];
|
|
|
|
|
[sql replaceCharactersInRange: NSMakeRange(0, 6)
|
|
|
|
|
withString: @""];
|
2004-07-26 08:56:26 +00:00
|
|
|
|
}
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
- (void) reset
|
|
|
|
|
{
|
|
|
|
|
[_info removeAllObjects];
|
|
|
|
|
_count = 0;
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@end
|
|
|
|
|
|