mirror of
https://github.com/gnustep/libs-sqlclient.git
synced 2025-02-14 15:40:59 +00:00
1526 lines
36 KiB
Objective-C
1526 lines
36 KiB
Objective-C
/* -*-objc-*- */
|
|
|
|
/** Implementation of SQLClientPool for GNUStep
|
|
Copyright (C) 2014 Free Software Foundation, Inc.
|
|
|
|
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
|
Date: June 2014
|
|
|
|
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: 2014-05-19 10:31:02 +0100 (Mon, 19 May 2014) $ $Revision: 37893 $
|
|
*/
|
|
|
|
#import <Foundation/NSArray.h>
|
|
#import <Foundation/NSAutoreleasePool.h>
|
|
#import <Foundation/NSDebug.h>
|
|
#import <Foundation/NSDictionary.h>
|
|
#import <Foundation/NSException.h>
|
|
#import <Foundation/NSInvocation.h>
|
|
#import <Foundation/NSLock.h>
|
|
#import <Foundation/NSString.h>
|
|
#import <Foundation/NSThread.h>
|
|
#import <Foundation/NSUserDefaults.h>
|
|
|
|
#import <Performance/GSCache.h>
|
|
#import "SQLClient.h"
|
|
|
|
struct _SQLClientPoolItem {
|
|
SQLClient *c; /** The clients of the pool. */
|
|
NSThread *o; /** The thread owning the client */
|
|
NSUInteger u; /** Count of client usage. */
|
|
NSTimeInterval t; /** When client was removed from pool. */
|
|
};
|
|
|
|
@interface SQLClient(Pool)
|
|
- (void) _clearPool: (SQLClientPool*)p;
|
|
- (void) _waitPool: (NSTimeInterval)ti;
|
|
@end
|
|
|
|
@implementation SQLClient(Pool)
|
|
- (void) _clearPool: (SQLClientPool*)p
|
|
{
|
|
NSAssert(_pool == p, NSInternalInconsistencyException);
|
|
|
|
_pool = nil;
|
|
}
|
|
- (void) _waitPool: (NSTimeInterval)ti
|
|
{
|
|
_waitPool = ti;
|
|
}
|
|
@end
|
|
|
|
@interface SQLClientPool (Adjust)
|
|
+ (void) _adjustPoolConnections: (int)n;
|
|
@end
|
|
|
|
@interface SQLClientPool (Private)
|
|
- (void) _lock;
|
|
- (SQLClient*) _provideClientBeforeDate: (NSDate*)when
|
|
exclusive: (BOOL)isLocal
|
|
blocked: (NSTimeInterval*)ti;
|
|
- (SQLClient*) _provide;
|
|
- (NSString*) _rc: (SQLClient*)o;
|
|
- (void) _unlock;
|
|
@end
|
|
|
|
@interface SQLTransaction (Creation)
|
|
+ (SQLTransaction*) _transactionUsing: (id)clientOrPool
|
|
batch: (BOOL)isBatched
|
|
stop: (BOOL)stopOnFailure;
|
|
@end
|
|
|
|
@implementation SQLClientPool
|
|
|
|
#if defined(GNUSTEP)
|
|
static Class cls = Nil;
|
|
|
|
+ (void) initialize
|
|
{
|
|
if (Nil == cls)
|
|
{
|
|
cls = [NSAutoreleasePool class];
|
|
}
|
|
}
|
|
#endif
|
|
|
|
- (int) availableConnections
|
|
{
|
|
int available;
|
|
int index;
|
|
|
|
[self _lock];
|
|
available = index = _max;
|
|
while (index-- > 0)
|
|
{
|
|
if (_items[index].u > 0)
|
|
{
|
|
available--;
|
|
}
|
|
}
|
|
[self _unlock];
|
|
return available;
|
|
}
|
|
|
|
- (SQLTransaction*) batch: (BOOL)stopOnFailure
|
|
{
|
|
return [SQLTransaction _transactionUsing: self
|
|
batch: YES
|
|
stop: stopOnFailure];
|
|
}
|
|
|
|
- (GSCache*) cache
|
|
{
|
|
return [_items[0].c cache];
|
|
}
|
|
|
|
- (uint64_t) committed
|
|
{
|
|
NSUInteger index;
|
|
uint64_t total = 0;
|
|
|
|
[_lock lock];
|
|
for (index = 0; index < _max; index++)
|
|
{
|
|
SQLClient *client = _items[index].c;
|
|
|
|
total += [client committed];
|
|
}
|
|
[_lock unlock];
|
|
return total;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
SQLClientPoolItem *old;
|
|
int count;
|
|
int i;
|
|
|
|
[_lock lock];
|
|
count = _max;
|
|
old = _items;
|
|
_max = 0;
|
|
_min = 0;
|
|
_items = 0;
|
|
if (0 != old)
|
|
{
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
[old[i].c _clearPool: self];
|
|
[old[i].o release];
|
|
if (0 == old[i].u)
|
|
{
|
|
[old[i].c release];
|
|
}
|
|
}
|
|
free(old);
|
|
}
|
|
[_lock unlock];
|
|
DESTROY(_lock);
|
|
DESTROY(_config);
|
|
DESTROY(_name);
|
|
[SQLClientPool _adjustPoolConnections: -count];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSString*) description
|
|
{
|
|
return [NSString stringWithFormat: @"%@ '%@'", [super description], _name];
|
|
}
|
|
|
|
- (id) initWithConfiguration: (NSDictionary*)config
|
|
name: (NSString*)reference
|
|
max: (int)maxConnections
|
|
min: (int)minConnections
|
|
{
|
|
if (nil != (self = [super init]))
|
|
{
|
|
if (nil == config)
|
|
{
|
|
config = (NSDictionary*)[NSUserDefaults standardUserDefaults];
|
|
}
|
|
ASSIGN(_config, config);
|
|
if (NO == [reference isKindOfClass: [NSString class]])
|
|
{
|
|
reference = [_config objectForKey: @"SQLClientName"];
|
|
if (NO == [reference isKindOfClass: [NSString class]])
|
|
{
|
|
reference = @"Database";
|
|
}
|
|
}
|
|
ASSIGNCOPY(_name, reference);
|
|
_lock = [[NSConditionLock alloc] initWithCondition: 0];
|
|
[self setMax: maxConnections min: minConnections];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSString*) longDescription
|
|
{
|
|
NSMutableString *s = [[NSMutableString new] autorelease];
|
|
|
|
[s appendString: [self description]];
|
|
[s appendString: @", "];
|
|
[s appendString: [self status]];
|
|
[s appendString: [self statistics]];
|
|
[s appendString: [_items[0].c description]];
|
|
return s;
|
|
}
|
|
|
|
- (int) maxConnections
|
|
{
|
|
return _max;
|
|
}
|
|
|
|
- (int) minConnections
|
|
{
|
|
return _min;
|
|
}
|
|
|
|
- (NSString*) name
|
|
{
|
|
return _name;
|
|
}
|
|
|
|
- (SQLClient*) provideClient
|
|
{
|
|
return [self provideClientBeforeDate: nil exclusive: NO];
|
|
}
|
|
|
|
- (SQLClient*) provideClientBeforeDate: (NSDate*)when
|
|
{
|
|
return [self provideClientBeforeDate: when exclusive: NO];
|
|
}
|
|
|
|
- (SQLClient*) provideClientBeforeDate: (NSDate*)when exclusive: (BOOL)isLocal
|
|
{
|
|
return [self _provideClientBeforeDate: when
|
|
exclusive: isLocal
|
|
blocked: (NSTimeInterval*)NULL];
|
|
}
|
|
|
|
- (SQLClient*) provideClientExclusive
|
|
{
|
|
return [self provideClientBeforeDate: nil exclusive: YES];
|
|
}
|
|
|
|
- (void) purge
|
|
{
|
|
BOOL more = YES;
|
|
|
|
[self _lock];
|
|
|
|
while (YES == more)
|
|
{
|
|
SQLClient *found = nil;
|
|
int connected = 0;
|
|
int index;
|
|
|
|
more = NO;
|
|
for (index = 0; index < _max; index++)
|
|
{
|
|
if (YES == [_items[index].c connected])
|
|
{
|
|
/* This is a connected client.
|
|
*/
|
|
connected++;
|
|
if (0 == _items[index].u)
|
|
{
|
|
/* Not in use; so a candidate to be purged
|
|
*/
|
|
found = [_items[index].c longestIdle: found];
|
|
}
|
|
}
|
|
}
|
|
if (nil != found)
|
|
{
|
|
NSTimeInterval age;
|
|
|
|
age = -[[found lastOperation] timeIntervalSinceNow];
|
|
if (_debugging > 2)
|
|
{
|
|
NSLog(@"%@ purge found %p age %g",
|
|
self, found, age);
|
|
}
|
|
if (age > _purgeAll
|
|
|| (connected > _min && age > _purgeMin))
|
|
{
|
|
NS_DURING
|
|
{
|
|
[found disconnect];
|
|
more = YES;
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Error disconnecting client in pool: %@",
|
|
localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
}
|
|
}
|
|
[self _unlock];
|
|
}
|
|
|
|
- (void) setCache: (GSCache*)aCache
|
|
{
|
|
int index;
|
|
|
|
/* We don't allow a nil cache for the pool (each client would create its
|
|
* own cache on demand). So we treat a nil cache as a request to create
|
|
* a new cache with the default config.
|
|
*/
|
|
[self _lock];
|
|
if (nil == aCache)
|
|
{
|
|
[_items[0].c setCache: nil];
|
|
aCache = [_items[0].c cache];
|
|
}
|
|
for (index = 1; index < _max; index++)
|
|
{
|
|
[_items[index].c setCache: aCache];
|
|
}
|
|
[self _unlock];
|
|
}
|
|
|
|
- (void) setCacheThread: (NSThread*)aThread
|
|
{
|
|
int index;
|
|
|
|
[self _lock];
|
|
for (index = 0; index < _max; index++)
|
|
{
|
|
[_items[index].c setCacheThread: aThread];
|
|
}
|
|
[self _unlock];
|
|
}
|
|
|
|
- (void) setDebugging: (unsigned int)level
|
|
{
|
|
int index;
|
|
|
|
[self _lock];
|
|
_debugging = level;
|
|
for (index = 0; index < _max; index++)
|
|
{
|
|
[_items[index].c setDebugging: _debugging];
|
|
}
|
|
[self _unlock];
|
|
}
|
|
|
|
- (void) setDurationLogging: (NSTimeInterval)threshold
|
|
{
|
|
int index;
|
|
|
|
[self _lock];
|
|
_duration = threshold;
|
|
for (index = 0; index < _max; index++)
|
|
{
|
|
[_items[index].c setDurationLogging: _duration];
|
|
}
|
|
[self _unlock];
|
|
}
|
|
|
|
- (void) setMax: (int)maxConnections min: (int)minConnections
|
|
{
|
|
int old;
|
|
int index;
|
|
|
|
if (minConnections < 1) minConnections = 1;
|
|
if (maxConnections < 1) maxConnections = 5;
|
|
if (maxConnections > 100) maxConnections = 100;
|
|
if (minConnections > maxConnections) minConnections = maxConnections;
|
|
|
|
[self _lock];
|
|
old = _max;
|
|
if (maxConnections != _max)
|
|
{
|
|
GSCache *cache = nil;
|
|
|
|
if (_max > 0)
|
|
{
|
|
while (_max > maxConnections)
|
|
{
|
|
_max--;
|
|
[_items[_max].c _clearPool: self];
|
|
if (0 == _items[_max].u)
|
|
{
|
|
[_items[_max].c release];
|
|
}
|
|
DESTROY(_items[_max].o);
|
|
}
|
|
_items = realloc(_items, maxConnections * sizeof(SQLClientPoolItem));
|
|
}
|
|
else
|
|
{
|
|
_items = calloc(maxConnections, sizeof(SQLClientPoolItem));
|
|
}
|
|
for (index = _max; index < maxConnections; index++)
|
|
{
|
|
_items[index].o = nil;
|
|
_items[index].t = 0.0;
|
|
_items[index].u = 0;
|
|
_items[index].c = [[SQLClient alloc] initWithConfiguration: _config
|
|
name: _name
|
|
pool: self];
|
|
|
|
/* All the clients in the pool should share the same cache.
|
|
*/
|
|
if (0 == index)
|
|
{
|
|
cache = [_items[index].c cache];
|
|
}
|
|
else
|
|
{
|
|
[_items[index].c setCache: cache];
|
|
}
|
|
}
|
|
_max = maxConnections;
|
|
[SQLClientPool _adjustPoolConnections: _max - old];
|
|
}
|
|
_min = minConnections;
|
|
[self _unlock];
|
|
}
|
|
|
|
- (void) setPurgeAll: (int)allSeconds min: (int)minSeconds
|
|
{
|
|
if (allSeconds < 1)
|
|
{
|
|
allSeconds = 300;
|
|
}
|
|
if (minSeconds < 1)
|
|
{
|
|
minSeconds = 10;
|
|
}
|
|
if (allSeconds < minSeconds)
|
|
{
|
|
allSeconds = minSeconds;
|
|
}
|
|
_purgeMin = minSeconds;
|
|
_purgeAll = allSeconds;
|
|
}
|
|
|
|
- (NSString*) statistics
|
|
{
|
|
NSString *s;
|
|
|
|
s = [NSString stringWithFormat:
|
|
@" Immediate provisions: %llu\n"
|
|
@" Delayed provisions: %llu\n"
|
|
@" Timed out provisions: %llu\n"
|
|
@" Slowest provision: %g\n"
|
|
@" Average delay: %g\n"
|
|
@" Average timeout: %g\n"
|
|
@" Average over all: %g\n"
|
|
@" Committed transactions: %"PRIu64"\n",
|
|
(unsigned long long)_immediate,
|
|
(unsigned long long)_delayed,
|
|
(unsigned long long)_failed,
|
|
_longest,
|
|
(_delayed > 0) ? _delayWaits / _delayed : 0.0,
|
|
(_failed > 0) ? _failWaits / _failed : 0.0,
|
|
(_immediate + _delayed + _failed) > 0
|
|
? (_failWaits + _delayWaits) / (_immediate + _delayed + _failed)
|
|
: 0.0,
|
|
[self committed]];
|
|
return s;
|
|
}
|
|
|
|
- (NSString*) status
|
|
{
|
|
NSMutableArray *idleInfo = nil;
|
|
NSMutableArray *liveInfo = nil;
|
|
NSMutableString *retainInfo = nil;
|
|
unsigned int cond = 0;
|
|
unsigned int free = 0;
|
|
unsigned int dead = 0;
|
|
unsigned int idle = 0;
|
|
unsigned int live = 0;
|
|
unsigned int used = 0;
|
|
int index;
|
|
NSMutableString *s;
|
|
|
|
[_lock lock];
|
|
for (index = 0; index < _max; index++)
|
|
{
|
|
SQLClient *client = _items[index].c;
|
|
NSUInteger rc = [client retainCount];
|
|
NSUInteger uc = _items[index].u;
|
|
|
|
/* Check to see if this client is free to be taken from the pool.
|
|
* Also, if a client is connected but not in use, we call it idle.
|
|
*/
|
|
if (uc > 0)
|
|
{
|
|
NSString *tmp;
|
|
|
|
if (NSNotFound == uc)
|
|
{
|
|
tmp = [NSString stringWithFormat: @" Client '%@'"
|
|
@" provided exclusively (retained:%"PRIuPTR,
|
|
[client name], rc];
|
|
}
|
|
else
|
|
{
|
|
tmp = [NSString stringWithFormat: @" Client '%@'"
|
|
@" provided %"PRIuPTR
|
|
@" time%s (retained:%"PRIuPTR,
|
|
[client name], uc, ((1 == uc) ? "" : "s"), rc];
|
|
}
|
|
|
|
#if defined(GNUSTEP)
|
|
NSUInteger ac = [cls autoreleaseCountForObject: client];
|
|
|
|
tmp = [tmp stringByAppendingFormat: @" - autoreleased:%"PRIuPTR, ac];
|
|
#endif
|
|
|
|
/* This is a client which has been provided by the pool,
|
|
* so it is in use by some code.
|
|
*/
|
|
if (YES == [client isInTransaction])
|
|
{
|
|
NSDate *d = [client lastOperation];
|
|
|
|
live++;
|
|
if (nil == liveInfo)
|
|
{
|
|
liveInfo = [NSMutableArray array];
|
|
}
|
|
[liveInfo addObject: [tmp stringByAppendingFormat:
|
|
@") active in transaction since %@\n", d]];
|
|
}
|
|
else
|
|
{
|
|
if (YES == [client connected])
|
|
{
|
|
NSDate *d = [client lastOperation];
|
|
|
|
used++;
|
|
if (nil == d)
|
|
{
|
|
d = [client lastConnect];
|
|
}
|
|
if ([d timeIntervalSinceReferenceDate] < _items[index].t)
|
|
{
|
|
d = [NSDate dateWithTimeIntervalSinceReferenceDate:
|
|
_items[index].t];
|
|
}
|
|
if (nil == idleInfo)
|
|
{
|
|
idleInfo = [NSMutableArray array];
|
|
}
|
|
[idleInfo addObject: [tmp stringByAppendingFormat:
|
|
@") taken from pool but idle since %@\n", d]];
|
|
}
|
|
else
|
|
{
|
|
idle++;
|
|
[idleInfo addObject: [tmp stringByAppendingFormat:
|
|
@") taken from pool and recently used\n"]];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BOOL connected = [_items[index].c connected];
|
|
|
|
/* The client is not in use and can be provided by the pool,
|
|
* so we must therefore re-lock with condition 1.
|
|
*/
|
|
cond = 1;
|
|
if (_debugging > 0)
|
|
{
|
|
if (nil == retainInfo)
|
|
{
|
|
retainInfo = [NSMutableString stringWithCapacity: 100];
|
|
}
|
|
[retainInfo appendFormat:
|
|
@" Client '%@' (retain count %"PRIuPTR
|
|
@") %s pool %s connected to server\n",
|
|
[client name], rc,
|
|
((_items[index].u > 0) ? "taken from" : "available in"),
|
|
((YES == connected) ? "and" : "but not")];
|
|
}
|
|
if (YES == connected)
|
|
{
|
|
/* Still connected, so we count it as a free connection.
|
|
*/
|
|
free++;
|
|
}
|
|
else
|
|
{
|
|
/* Not connected, so we count it as a dead connection.
|
|
*/
|
|
dead++;
|
|
}
|
|
}
|
|
}
|
|
|
|
s = [NSMutableString stringWithFormat: @" size min: %u, max: %u\n"
|
|
@" live:%u, used:%u, idle:%u, free:%u, dead:%u\n",
|
|
_min, _max, live, used, idle, free, dead];
|
|
|
|
if (liveInfo)
|
|
{
|
|
for (index = 0; index < [liveInfo count]; index++)
|
|
{
|
|
[s appendString: [liveInfo objectAtIndex: index]];
|
|
}
|
|
}
|
|
if (idleInfo)
|
|
{
|
|
for (index = 0; index < [idleInfo count]; index++)
|
|
{
|
|
[s appendString: [idleInfo objectAtIndex: index]];
|
|
}
|
|
}
|
|
if (retainInfo)
|
|
{
|
|
[s appendString: retainInfo];
|
|
}
|
|
[_lock unlockWithCondition: cond];
|
|
return s;
|
|
}
|
|
|
|
- (BOOL) _swallowClient: (SQLClient*)client explicit: (BOOL)swallowed
|
|
{
|
|
BOOL found = NO;
|
|
int index;
|
|
|
|
if (YES == [client isInTransaction])
|
|
{
|
|
/* The client has a transaction in progress ... if it's in the
|
|
* current thread we should be able to disconnect (implicit rollback)
|
|
* and return the client to the pool, otherwise we raise an exception.
|
|
*/
|
|
if (YES == [client lockBeforeDate: nil])
|
|
{
|
|
[client disconnect];
|
|
[client unlock];
|
|
NSLog(@"ERROR: Disconnected client which was returned to pool"
|
|
@" while a transaction was in progress: %@", client);
|
|
}
|
|
else
|
|
{
|
|
[NSException raise: SQLConnectionException
|
|
format: @"failed to return to pool because a transaction"
|
|
@" was in progress: %@", client];
|
|
}
|
|
}
|
|
/* The client must not have any observers if it's in a pool.
|
|
*/
|
|
[client removeObserver: nil name: nil];
|
|
[self _lock];
|
|
for (index = 0; index < _max && NO == found; index++)
|
|
{
|
|
if (_items[index].u > 0 && client == _items[index].c)
|
|
{
|
|
found = YES;
|
|
if (YES == swallowed)
|
|
{
|
|
if (NSNotFound == _items[index].u || 1 == _items[index].u)
|
|
{
|
|
/* This was only provided once, and has been explicitly
|
|
* swallowed by the pool again, so we should increment
|
|
* the reference count to prevent an implicit swallow
|
|
* caused by deallocation.
|
|
*/
|
|
_items[index].u = 0;
|
|
if (client)
|
|
{
|
|
NSIncrementExtraRefCount(client);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_items[index].u--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Nothing is using this client connection any more (it had
|
|
* -dealloc called), so we know the count must be zero.
|
|
*/
|
|
_items[index].u = 0;
|
|
}
|
|
if (0 == _items[index].u)
|
|
{
|
|
DESTROY(_items[index].o);
|
|
}
|
|
}
|
|
}
|
|
[self _unlock];
|
|
|
|
if (_debugging > 2)
|
|
{
|
|
if (YES == found)
|
|
{
|
|
NSLog(@"%@ swallows %p%@", self, client, [self _rc: client]);
|
|
}
|
|
else
|
|
{
|
|
NSLog(@"%@ rejects %p%@", self, client, [self _rc: client]);
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
- (BOOL) swallowClient: (SQLClient*)client
|
|
{
|
|
return [self _swallowClient: client explicit: YES];
|
|
}
|
|
|
|
- (void) setClientName: (NSString*)s
|
|
{
|
|
unsigned int index;
|
|
|
|
[self _lock];
|
|
for (index = 0; index < _max; index++)
|
|
{
|
|
SQLClient *client = _items[index].c;
|
|
|
|
if (nil == s)
|
|
{
|
|
[client setClientName: s];
|
|
}
|
|
else
|
|
{
|
|
NSString *n;
|
|
|
|
n = [s stringByAppendingFormat: @" (%u)", index];
|
|
[client setClientName: n];
|
|
}
|
|
}
|
|
[self _unlock];
|
|
}
|
|
|
|
- (SQLTransaction*) transaction
|
|
{
|
|
return [SQLTransaction _transactionUsing: self
|
|
batch: NO
|
|
stop: NO];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation SQLClientPool (Private)
|
|
|
|
- (void) _lock
|
|
{
|
|
[_lock lock];
|
|
}
|
|
|
|
- (SQLClient*) _provideClientBeforeDate: (NSDate*)when
|
|
exclusive: (BOOL)isLocal
|
|
blocked: (NSTimeInterval*)ti
|
|
{
|
|
NSThread *thread = [NSThread currentThread];
|
|
NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
|
|
NSTimeInterval now = start;
|
|
NSTimeInterval block = 0.0;
|
|
SQLClient *client = nil;
|
|
int preferred = -1;
|
|
int found = -1;
|
|
int cond = 0;
|
|
int index;
|
|
|
|
/* If this is a request for a non-exclusive connection, we can simply
|
|
* check to see if there's already such a connection available.
|
|
*/
|
|
if (NO == isLocal)
|
|
{
|
|
if (NO == [_lock tryLock])
|
|
{
|
|
[_lock lock];
|
|
block = start;
|
|
}
|
|
for (index = 0; index < _max; index++)
|
|
{
|
|
if (_items[index].o == thread && _items[index].u < NSNotFound
|
|
&& NO == [_items[index].c isInTransaction])
|
|
{
|
|
preferred = index; // Ignore any other connected client
|
|
break;
|
|
}
|
|
if (nil == _items[index].o && 0 == _items[index].u)
|
|
{
|
|
if (preferred < 0 && YES == [_items[index].c connected])
|
|
{
|
|
preferred = index;
|
|
}
|
|
else
|
|
{
|
|
found = index;
|
|
}
|
|
}
|
|
}
|
|
if (preferred >= 0)
|
|
{
|
|
found = preferred; // Prefer a connected client.
|
|
}
|
|
if (found >= 0)
|
|
{
|
|
_items[found].t = now;
|
|
if (0 == _items[found].u++)
|
|
{
|
|
ASSIGN(_items[found].o, thread);
|
|
client = [_items[found].c autorelease];
|
|
}
|
|
else
|
|
{
|
|
/* We have already provided this client, so we must retain it
|
|
* before we autorelease it, to keep retain counts in sync.
|
|
*/
|
|
client = [[_items[found].c retain] autorelease];
|
|
}
|
|
_immediate++;
|
|
}
|
|
[self _unlock];
|
|
if (nil != client)
|
|
{
|
|
if (_debugging > 2)
|
|
{
|
|
NSLog(@"%@ provides %p%@",
|
|
self, _items[found].c, [self _rc: client]);
|
|
}
|
|
if (ti)
|
|
{
|
|
*ti = block;
|
|
}
|
|
return client;
|
|
}
|
|
}
|
|
|
|
/* If we haven't been given a timeout, we should wait for a client
|
|
* indefinitely ... so we set the timeout to be in the distant future.
|
|
*/
|
|
if (nil == when)
|
|
{
|
|
static NSDate *future = nil;
|
|
|
|
if (nil == future)
|
|
{
|
|
future = RETAIN([NSDate distantFuture]);
|
|
}
|
|
when = future;
|
|
}
|
|
|
|
/* We want to log stuff if we don't get a client quickly.
|
|
* Ideally we get the lock straight away,
|
|
* but if not we want to log every ten seconds (and possibly
|
|
* when we begin waiting).
|
|
*/
|
|
if ([_lock tryLockWhenCondition: 1])
|
|
{
|
|
_immediate++;
|
|
}
|
|
else
|
|
{
|
|
NSTimeInterval end = [when timeIntervalSinceReferenceDate];
|
|
NSTimeInterval dif = 0.0;
|
|
NSDate *until;
|
|
BOOL locked;
|
|
|
|
block = start;
|
|
if (_debugging > 1)
|
|
{
|
|
NSLog(@"%@ has no clients available", self);
|
|
}
|
|
until = [[NSDate alloc]
|
|
initWithTimeIntervalSinceReferenceDate: now + 10.0];
|
|
locked = NO;
|
|
while (NO == locked && now < end)
|
|
{
|
|
if (now >= end)
|
|
{
|
|
/* End date is passed ... try to get the lock immediately.
|
|
*/
|
|
locked = [_lock tryLockWhenCondition: 1];
|
|
}
|
|
else if ([when earlierDate: until] == until)
|
|
{
|
|
locked = [_lock lockWhenCondition: 1 beforeDate: until];
|
|
}
|
|
else
|
|
{
|
|
locked = [_lock lockWhenCondition: 1 beforeDate: when];
|
|
}
|
|
now = [NSDate timeIntervalSinceReferenceDate];
|
|
dif = now - start;
|
|
if (NO == locked && now < end)
|
|
{
|
|
if (_debugging > 0 || dif > 30.0
|
|
|| (_duration >= 0.0 && dif > _duration))
|
|
{
|
|
NSLog(@"%@ still waiting after %g seconds:\n%@",
|
|
self, dif, [self status]);
|
|
}
|
|
[until release];
|
|
until = [[NSDate alloc] initWithTimeIntervalSinceNow: 10.0];
|
|
}
|
|
}
|
|
[until release];
|
|
if (dif > _longest)
|
|
{
|
|
_longest = dif;
|
|
}
|
|
if (NO == locked)
|
|
{
|
|
if (_debugging > 0 || dif > 30.0
|
|
|| (_duration >= 0.0 && dif > _duration))
|
|
{
|
|
NSLog(@"%@ abandoned wait after %g seconds:\n%@",
|
|
self, dif, [self status]);
|
|
}
|
|
_failed++;
|
|
_failWaits += dif;
|
|
if (ti)
|
|
{
|
|
*ti = block;
|
|
}
|
|
return nil;
|
|
}
|
|
if (_debugging > 0 || (_duration >= 0.0 && dif > _duration))
|
|
{
|
|
NSLog(@"%@ provided client after %g seconds",
|
|
self, dif);
|
|
}
|
|
_delayed++;
|
|
_delayWaits += dif;
|
|
}
|
|
|
|
for (index = 0; index < _max && 0 == cond; index++)
|
|
{
|
|
if (0 == _items[index].u)
|
|
{
|
|
if (preferred >= 0 || found >= 0)
|
|
{
|
|
/* There's at least one more client available to be
|
|
* provided, so we want to re-lock with condition 1.
|
|
*/
|
|
cond = 1;
|
|
}
|
|
if (preferred < 0 && YES == [_items[index].c connected])
|
|
{
|
|
preferred = index;
|
|
}
|
|
else
|
|
{
|
|
found = index;
|
|
}
|
|
}
|
|
else if (NO == isLocal
|
|
&& _items[index].o == thread
|
|
&& _items[index].u < NSNotFound
|
|
&& NO == [_items[index].c isInTransaction])
|
|
{
|
|
/* We are allowed to re-use connections in the current thread,
|
|
* so if we have found one, treat it as the preferred choice.
|
|
*/
|
|
preferred = index;
|
|
}
|
|
}
|
|
|
|
/* We prefer to use a client which is already connected, so we
|
|
* avoid opening unnecessary connections.
|
|
*/
|
|
if (preferred >= 0)
|
|
{
|
|
found = preferred;
|
|
}
|
|
if (YES == isLocal)
|
|
{
|
|
_items[found].u = NSNotFound;
|
|
}
|
|
else
|
|
{
|
|
_items[found].u++;
|
|
}
|
|
_items[found].t = now;
|
|
ASSIGN(_items[found].o, thread);
|
|
[self _unlock];
|
|
client = [_items[found].c autorelease];
|
|
if (_debugging > 2)
|
|
{
|
|
NSLog(@"%@ provides %p%@", self, _items[found].c, [self _rc: client]);
|
|
}
|
|
if (ti)
|
|
{
|
|
*ti = block;
|
|
}
|
|
return client;
|
|
}
|
|
|
|
- (SQLClient*) _provide
|
|
{
|
|
NSTimeInterval start;
|
|
SQLClient *db;
|
|
|
|
db = [self _provideClientBeforeDate: nil
|
|
exclusive: NO
|
|
blocked: &start];
|
|
[db _waitPool: start];
|
|
return db;
|
|
}
|
|
|
|
- (NSString*) _rc: (SQLClient*)o
|
|
{
|
|
#if defined(GNUSTEP)
|
|
if (_debugging > 3)
|
|
{
|
|
NSUInteger rc;
|
|
NSUInteger ac;
|
|
NSUInteger uc;
|
|
int index;
|
|
|
|
rc = [o retainCount];
|
|
ac = [cls autoreleaseCountForObject: o];
|
|
[_lock lock];
|
|
uc = 0;
|
|
for (index = 0; index < _max; index++)
|
|
{
|
|
if (o == _items[index].c)
|
|
{
|
|
uc = _items[index].u;
|
|
break;
|
|
}
|
|
}
|
|
[self _unlock];
|
|
if (NSNotFound == uc)
|
|
{
|
|
return [NSString stringWithFormat:
|
|
@" provided exclusively (retained:%"PRIuPTR
|
|
@" - autoreleased:%"PRIuPTR
|
|
@")",
|
|
rc, ac];
|
|
}
|
|
else
|
|
{
|
|
return [NSString stringWithFormat:
|
|
@" provided %"PRIuPTR
|
|
@" time%s (retained:%"PRIuPTR
|
|
@" - autoreleased:%"PRIuPTR
|
|
@")",
|
|
uc, ((1 == uc) ? "" : "s"), rc, ac];
|
|
}
|
|
}
|
|
#endif
|
|
return @"";
|
|
}
|
|
|
|
- (void) _unlock
|
|
{
|
|
int index;
|
|
|
|
for (index = 0; index < _max; index++)
|
|
{
|
|
/* Check to see if this client is free to be taken from the pool.
|
|
*/
|
|
if (0 == _items[index].u)
|
|
{
|
|
[_lock unlockWithCondition: 1];
|
|
return;
|
|
}
|
|
}
|
|
[_lock unlockWithCondition: 0];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation SQLClientPool (ConvenienceMethods)
|
|
|
|
- (SQLLiteral*) buildQuery: (NSString*)stmt, ...
|
|
{
|
|
SQLLiteral *sql;
|
|
va_list ap;
|
|
|
|
/*
|
|
* First check validity and concatenate parts of the query.
|
|
*/
|
|
va_start (ap, stmt);
|
|
sql = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
|
|
va_end (ap);
|
|
|
|
return sql;
|
|
}
|
|
|
|
- (SQLLiteral*) buildQuery: (NSString*)stmt with: (NSDictionary*)values
|
|
{
|
|
SQLLiteral *sql;
|
|
|
|
sql = [_items[0].c buildQuery: stmt with: values];
|
|
|
|
return sql;
|
|
}
|
|
|
|
- (NSMutableArray*) cacheCheckSimpleQuery: (NSString*)stmt
|
|
{
|
|
NSMutableArray *result = [[_items[0].c cache] objectForKey: stmt];
|
|
|
|
if (result != nil)
|
|
{
|
|
result = [[result mutableCopy] autorelease];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
|
query: (NSString*)stmt,...
|
|
{
|
|
SQLClient *db;
|
|
NSMutableArray *result;
|
|
SQLLiteral *query;
|
|
va_list ap;
|
|
|
|
va_start (ap, stmt);
|
|
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
|
|
va_end (ap);
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db cache: seconds simpleQuery: query];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
return result;
|
|
}
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
|
query: (NSString*)stmt
|
|
with: (NSDictionary*)values
|
|
{
|
|
SQLClient *db;
|
|
NSMutableArray *result;
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db cache: seconds query: stmt with: values];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
return result;
|
|
}
|
|
|
|
- (NSMutableArray*) cache: (int)seconds simpleQuery: (SQLLitArg*)stmt;
|
|
{
|
|
SQLClient *db;
|
|
NSMutableArray *result;
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db cache: seconds simpleQuery: stmt];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
return result;
|
|
}
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
|
simpleQuery: (SQLLitArg*)stmt
|
|
recordType: (id)rtype
|
|
listType: (id)ltype
|
|
{
|
|
SQLClient *db;
|
|
NSMutableArray *result;
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db cache: seconds
|
|
simpleQuery: stmt
|
|
recordType: rtype
|
|
listType: ltype];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
return result;
|
|
}
|
|
|
|
- (NSMutableArray*) columns: (NSMutableArray*)records
|
|
{
|
|
return [SQLClient columns: records];
|
|
}
|
|
|
|
- (NSInteger) execute: (NSString*)stmt, ...
|
|
{
|
|
SQLClient *db;
|
|
NSInteger result;
|
|
NSArray *info;
|
|
va_list ap;
|
|
|
|
va_start (ap, stmt);
|
|
info = [_items[0].c prepare: stmt args: ap];
|
|
va_end (ap);
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db simpleExecute: info];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
return result;
|
|
}
|
|
|
|
- (NSInteger) execute: (NSString*)stmt with: (NSDictionary*)values
|
|
{
|
|
SQLClient *db;
|
|
NSInteger result;
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db execute: stmt with: values];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
return result;
|
|
}
|
|
|
|
- (SQLClientPool*) pool
|
|
{
|
|
return self;
|
|
}
|
|
|
|
- (NSMutableArray*) prepare: (NSString*)stmt, ...
|
|
{
|
|
va_list ap;
|
|
NSMutableArray *result;
|
|
|
|
va_start (ap, stmt);
|
|
result = [_items[0].c prepare: stmt args: ap];
|
|
va_end (ap);
|
|
|
|
return result;
|
|
}
|
|
|
|
- (NSMutableArray*) prepare: (NSString*)stmt args: (va_list)args
|
|
{
|
|
return [_items[0].c prepare: stmt args: args];
|
|
}
|
|
|
|
- (NSMutableArray*) prepare: (NSString*)stmt with: (NSDictionary*)values
|
|
{
|
|
return [_items[0].c prepare: stmt with: values];
|
|
}
|
|
|
|
- (NSMutableArray*) query: (NSString*)stmt, ...
|
|
{
|
|
SQLClient *db;
|
|
NSMutableArray *result;
|
|
SQLLiteral *query;
|
|
va_list ap;
|
|
|
|
/*
|
|
* First check validity and concatenate parts of the query.
|
|
*/
|
|
va_start (ap, stmt);
|
|
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
|
|
va_end (ap);
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db simpleQuery: query];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (NSMutableArray*) query: (NSString*)stmt with: (NSDictionary*)values
|
|
{
|
|
SQLClient *db;
|
|
NSMutableArray *result;
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db query: stmt with: values];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
return result;
|
|
}
|
|
|
|
- (SQLRecord*) queryRecord: (NSString*)stmt, ...
|
|
{
|
|
SQLClient *db;
|
|
NSArray *result;
|
|
SQLRecord *record;
|
|
SQLLiteral *query;
|
|
va_list ap;
|
|
|
|
va_start (ap, stmt);
|
|
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
|
|
va_end (ap);
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db simpleQuery: query];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
|
|
if ([result count] > 1)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Query returns more than one record -\n%@\n", query];
|
|
}
|
|
record = [result lastObject];
|
|
if (record == nil)
|
|
{
|
|
[NSException raise: SQLEmptyException
|
|
format: @"Query returns no data -\n%@\n", query];
|
|
}
|
|
return record;
|
|
}
|
|
|
|
- (NSString*) queryString: (NSString*)stmt, ...
|
|
{
|
|
SQLClient *db;
|
|
NSArray *result;
|
|
SQLRecord *record;
|
|
SQLLiteral *query;
|
|
va_list ap;
|
|
|
|
va_start (ap, stmt);
|
|
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
|
|
va_end (ap);
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db simpleQuery: query];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
|
|
if ([result count] > 1)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Query returns more than one record -\n%@\n", query];
|
|
}
|
|
record = [result lastObject];
|
|
if (record == nil)
|
|
{
|
|
[NSException raise: SQLEmptyException
|
|
format: @"Query returns no data -\n%@\n", query];
|
|
}
|
|
if ([record count] > 1)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Query returns multiple fields -\n%@\n", query];
|
|
}
|
|
return [[record lastObject] description];
|
|
}
|
|
|
|
- (SQLLiteral*) quote: (id)obj
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quote: obj];
|
|
}
|
|
|
|
- (SQLLiteral*) quoteArray: (NSArray*)a
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quoteArray: a];
|
|
}
|
|
|
|
- (SQLLiteral*) quoteArraySafe: (NSArray*)a
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quoteArraySafe: a];
|
|
}
|
|
|
|
- (NSMutableString*) quoteArray: (NSArray *)a
|
|
toString: (NSMutableString *)s
|
|
quotingStrings: (BOOL)_q
|
|
{
|
|
NSMutableString *result;
|
|
|
|
result = [_items[0].c quoteArray: a toString:s quotingStrings: _q];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (SQLLiteral*) quotef: (NSString*)fmt, ...
|
|
{
|
|
va_list ap;
|
|
NSString *str;
|
|
SQLLiteral *quoted;
|
|
|
|
va_start(ap, fmt);
|
|
str = [[NSString allocWithZone: NSDefaultMallocZone()]
|
|
initWithFormat: fmt arguments: ap];
|
|
va_end(ap);
|
|
quoted = (SQLLiteral*)[_items[0].c quoteString: str];
|
|
[str release];
|
|
return (SQLLiteral*)quoted;
|
|
}
|
|
|
|
- (SQLLiteral*) quoteBigInteger: (int64_t)i
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quoteBigInteger: i];
|
|
}
|
|
|
|
- (SQLLiteral*) quoteCString: (const char *)s
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quoteCString: s];
|
|
}
|
|
|
|
- (SQLLiteral*) quoteChar: (char)chr
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quoteChar: chr];
|
|
}
|
|
|
|
- (SQLLiteral*) quoteFloat: (double)f
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quoteFloat: f];
|
|
}
|
|
|
|
- (SQLLiteral*) quoteInteger: (int)i
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quoteInteger: i];
|
|
}
|
|
|
|
- (SQLLiteral*) quoteName: (NSString *)s
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quoteName: s];
|
|
}
|
|
|
|
- (SQLLiteral*) quoteSet: (id)obj
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quoteSet: obj];
|
|
}
|
|
|
|
- (SQLLiteral*) quoteString: (NSString *)s
|
|
{
|
|
return (SQLLiteral*)[_items[0].c quoteString: s];
|
|
}
|
|
|
|
- (NSInteger) simpleExecute: (NSArray*)info
|
|
{
|
|
SQLClient *db;
|
|
NSInteger result;
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db simpleExecute: info];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
return result;
|
|
}
|
|
|
|
- (NSMutableArray*) simpleQuery: (SQLLitArg*)stmt
|
|
{
|
|
SQLClient *db;
|
|
NSMutableArray *result;
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db simpleQuery: stmt];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
return result;
|
|
}
|
|
|
|
- (NSMutableArray*) simpleQuery: (SQLLitArg*)stmt
|
|
recordType: (id)rtype
|
|
listType: (id)ltype
|
|
{
|
|
SQLClient *db;
|
|
NSMutableArray *result;
|
|
|
|
db = [self _provide];
|
|
NS_DURING
|
|
result = [db simpleQuery: stmt
|
|
recordType: rtype
|
|
listType: ltype];
|
|
NS_HANDLER
|
|
[self swallowClient: db];
|
|
[localException raise];
|
|
NS_ENDHANDLER
|
|
[self swallowClient: db];
|
|
return result;
|
|
}
|
|
|
|
- (void) singletons: (NSMutableArray*)records
|
|
{
|
|
[SQLClient singletons: records];
|
|
}
|
|
|
|
@end
|
|
|