Report lock delays as part of query duration logging

This commit is contained in:
rfm 2023-11-23 10:07:23 +00:00
parent 218578aa95
commit 16d56c5395
4 changed files with 471 additions and 327 deletions

View file

@ -1,3 +1,13 @@
2023-11-23 Richard Frith-Macdonald <rfm@gnu.org>
* SQLClient.h: New instance variables for timing lock waits.
* SQLClient.m: Record duration of waits for locks.
* SQLClientPool.m: Tell client when pool was locked before query.
Changes so that, the time spent waiting to obtain a lock to get a
connection from a pool or to be able to execute a query on a
connection used by another thread is recorded. When query duration
logging is performed, report lock delays of over a millisecond.
2023-08-16 Richard Frith-Macdonald <rfm@gnu.org>
* Postgres.m: Implement support for connect_timeoout= option to

View file

@ -521,6 +521,8 @@ SQLCLIENT_PRIVATE
NSTimeInterval _lastOperation;
NSTimeInterval _lastConnect; /** Last successful connect */
NSTimeInterval _lastStart; /** Last op start or connect */
NSTimeInterval _waitLock; /** When we blocked for locking */
NSTimeInterval _waitPool; /** When we blocked for pool access */
NSTimeInterval _duration; /** Duration logging threshold */
uint64_t _committed; /** Count of committed transactions */
unsigned int _debugging; /** The current debugging level */

View file

@ -1195,6 +1195,15 @@ static NSArray *rollbackStatement = nil;
@interface SQLClient (Private)
/* Takes the timestamp of the end of the operation and checks how long
* the operation took, returning a string containing a message to be
* logged if the threshold was exceeded.
* The message contains the total duration with a separate breakdown
* of connection and pool lock wait times if they are a millisecond
* or longer.
*/
- (NSMutableString*) _checkDuration: (NSTimeInterval)end;
/**
* Internal method to handle configuration using the notification object.
* This object may be either a configuration front end or a user defaults
@ -2795,12 +2804,13 @@ static int poolConnections = 0;
- (NSInteger) simpleExecute: (id)info
{
NSInteger result;
NSString *debug = nil;
BOOL done = NO;
BOOL isCommit = NO;
BOOL isRollback = NO;
NSString *statement;
NSInteger result;
NSString *debug = nil;
BOOL done = NO;
BOOL isCommit = NO;
BOOL isRollback = NO;
NSString *statement;
NSTimeInterval wait = 0.0;
if ([info isKindOfClass: NSArrayClass] == NO)
{
@ -2815,7 +2825,12 @@ static int poolConnections = 0;
info = [NSMutableArray arrayWithObject: info];
}
[lock lock];
if (NO == [lock tryLock])
{
wait = GSTickerTimeNow();
[lock lock];
}
_waitLock = wait;
statement = [info objectAtIndex: 0];
@ -2823,6 +2838,8 @@ static int poolConnections = 0;
*/
if ([self connect] == NO)
{
_waitPool = 0.0;
_waitLock = 0.0;
[lock unlock];
[NSException raise: SQLConnectionException
format: @"Unable to connect to '%@' to run statement %@",
@ -2844,60 +2861,51 @@ static int poolConnections = 0;
done = YES;
NS_DURING
{
NSMutableString *m;
_lastStart = GSTickerTimeNow();
result = [self backendExecute: info];
_lastOperation = GSTickerTimeNow();
[_statements addObject: statement];
if (_duration >= 0)
m = [self _checkDuration: _lastOperation];
if (m)
{
NSTimeInterval d;
if (isCommit || isRollback)
{
NSEnumerator *e = [_statements objectEnumerator];
d = _lastOperation - _lastStart;
if (d >= _duration)
{
NSMutableString *m;
if (isCommit || isRollback)
{
NSEnumerator *e = [_statements objectEnumerator];
if (isCommit)
{
m = [NSMutableString stringWithFormat:
@"Duration %g for transaction commit ...\n", d];
}
else
{
m = [NSMutableString stringWithFormat:
@"Duration %g for transaction rollback ...\n", d];
}
while ((statement = [e nextObject]) != nil)
{
[m appendFormat: @" %@;\n", statement];
}
[m appendFormat: @" affected %"PRIdPTR" record%s\n",
result, ((1 == result) ? "" : "s")];
}
else if ([self debugging] > 1)
{
/*
* For higher debug levels, we log data objects as well
* as the query string, otherwise we omit them.
*/
m = [NSMutableString stringWithFormat:
@"Duration %g for statement %@;", d, info];
[m appendFormat: @" affected %"PRIdPTR" record%s",
result, ((1 == result) ? "" : "s")];
}
else
{
m = [NSMutableString stringWithFormat:
@"Duration %g for statement %@;", d, statement];
[m appendFormat: @" affected %"PRIdPTR" record%s",
result, ((1 == result) ? "" : "s")];
}
debug = m;
}
if (isCommit)
{
[m appendString: @" for transaction commit ...\n"];
}
else
{
[m appendString: @" for transaction rollback ...\n"];
}
while ((statement = [e nextObject]) != nil)
{
[m appendFormat: @" %@;\n", statement];
}
[m appendFormat: @" affected %"PRIdPTR" record%s\n",
result, ((1 == result) ? "" : "s")];
}
else if ([self debugging] > 1)
{
/*
* For higher debug levels, we log data objects as well
* as the query string, otherwise we omit them.
*/
[m appendFormat: @" for statement %@;", info];
[m appendFormat: @" affected %"PRIdPTR" record%s",
result, ((1 == result) ? "" : "s")];
}
else
{
[m appendFormat: @" for statement %@;", statement];
[m appendFormat: @" affected %"PRIdPTR" record%s",
result, ((1 == result) ? "" : "s")];
}
debug = m;
}
if (_inTransaction == NO)
{
@ -2952,10 +2960,17 @@ static int poolConnections = 0;
NSMutableArray *result = nil;
NSString *debug = nil;
BOOL done = NO;
NSTimeInterval wait = 0.0;
if (rtype == 0) rtype = rClass;
if (ltype == 0) ltype = aClass;
[lock lock];
if (NO == [lock tryLock])
{
wait = GSTickerTimeNow();
[lock lock];
}
_waitLock = wait;
if ([self connect] == NO)
{
[lock unlock];
@ -2968,22 +2983,19 @@ static int poolConnections = 0;
done = YES;
NS_DURING
{
NSMutableString *m;
_lastStart = GSTickerTimeNow();
result = [self backendQuery: stmt recordType: rtype listType: ltype];
_lastOperation = GSTickerTimeNow();
if (_duration >= 0)
m = [self _checkDuration: _lastOperation];
if (m)
{
NSTimeInterval d;
NSUInteger count = [result count];
d = _lastOperation - _lastStart;
if (d >= _duration)
{
NSUInteger count = [result count];
debug = [NSString stringWithFormat:
@"Duration %g for query %@; produced %"PRIuPTR" record%s",
d, stmt, count, ((1 == count) ? "" : "s")];
}
[m appendFormat: @" for query %@; produced %"PRIuPTR" record%s",
stmt, count, ((1 == count) ? "" : "s")];
debug = m;
}
if (_inTransaction == NO)
{
@ -3027,8 +3039,25 @@ static int poolConnections = 0;
{
if (NO == connected)
{
[lock lock];
if (NO == connected)
NSTimeInterval wait = 0.0;
NSString *msg;
if (NO == [lock tryLock])
{
wait = GSTickerTimeNow();
[lock lock];
}
_waitLock = wait;
_lastStart = GSTickerTimeNow();
if (connected)
{
msg = [self _checkDuration: _lastStart];
if (msg)
{
[self debug: @"%@ for existing connection.", msg];
}
}
else
{
NS_DURING
{
@ -3052,7 +3081,6 @@ static int poolConnections = 0;
}
}
_lastStart = GSTickerTimeNow();
if (YES == [self backendConnect])
{
/* On establishing a new connection, we must restore any
@ -3071,43 +3099,39 @@ static int poolConnections = 0;
}
}
_lastConnect = GSTickerTimeNow();
msg = [self _checkDuration: _lastConnect];
_connectFails = 0;
}
else
{
_lastOperation = GSTickerTimeNow();
msg = [self _checkDuration: _lastOperation];
_connectFails++;
}
if (_duration >= 0)
if (msg)
{
NSTimeInterval d;
NSString *s;
NSString *s;
if (0 == _connectFails)
{
s = @"success";
d = _lastConnect - _lastStart;
}
else
{
s = @"failure";
d = _lastOperation - _lastStart;
}
if (d >= _duration)
if (_lastListen > 0.0)
{
if (_lastListen > 0.0)
{
[self debug: @"Duration %g for connection (%@)"
@", of which %g adding observers.",
d, s, _lastOperation - _lastListen];
}
else
{
[self debug: @"Duration %g for connection (%@).",
d, s];
}
[self debug: @"%@ for connection (%@)"
@", of which %g adding observers.",
msg, s, _lastOperation - _lastListen];
}
else
{
[self debug: @"%@ for connection (%@).",
msg, s];
}
}
}
@ -3274,6 +3298,69 @@ static int poolConnections = 0;
@implementation SQLClient (Private)
- (NSMutableString*) _checkDuration: (NSTimeInterval)end
{
NSMutableString *m = nil;
NSTimeInterval ti = _lastStart;
if (_waitLock > 0.0 && _waitLock < ti)
{
ti = _waitLock;
}
if (_waitPool > 0.0 && _waitPool < ti)
{
ti = _waitPool;
}
ti = end - ti; // Total duration
if (ti > _duration)
{
BOOL additional = NO;
m = [NSMutableString stringWithCapacity: 1000];
[m appendFormat: @"Duration %g", ti];
if (_waitPool > 0.0)
{
if (_waitLock > 0.0)
{
ti = _waitLock - _waitPool;
}
else
{
ti = _lastStart - _waitPool;
}
if (ti >= 0.001)
{
[m appendFormat: @" (%g waiting for connection pool", ti];
additional = YES;
}
}
if (_waitLock > 0.0)
{
ti = _lastStart - _waitLock;
if (ti >= 0.001)
{
if (additional)
{
[m appendFormat: @", %g waiting for connection lock", ti];
}
else
{
[m appendFormat: @" (%g waiting for connection lock", ti];
}
additional = YES;
}
}
if (additional)
{
[m appendString: @")"];
}
}
_waitPool = 0.0;
_waitLock = 0.0;
return m;
}
- (void) _configure: (NSNotification*)n
{
NSDictionary *o;
@ -3746,6 +3833,8 @@ static int poolConnections = 0;
GSCache *c;
id toCache;
BOOL cacheHit;
NSTimeInterval start;
NSString *s;
if (rtype == 0) rtype = rClass;
if (ltype == 0) ltype = aClass;
@ -3753,7 +3842,7 @@ static int poolConnections = 0;
md = [[NSThread currentThread] threadDictionary];
[md setObject: rtype forKey: @"SQLClientRecordType"];
[md setObject: ltype forKey: @"SQLClientListType"];
_lastStart = GSTickerTimeNow();
start = GSTickerTimeNow();
c = [self cache];
toCache = nil;
@ -3821,17 +3910,12 @@ static int poolConnections = 0;
result = [[result mutableCopy] autorelease];
}
_lastStart = start;
_lastOperation = GSTickerTimeNow();
if (_duration >= 0)
if ((s = [self _checkDuration: _lastOperation]) != nil)
{
NSTimeInterval d;
d = _lastOperation - _lastStart;
if (d >= _duration)
{
[self debug: @"Duration %g for cache-%@ query %@",
d, (YES == cacheHit) ? @"hit" : @"miss", stmt];
}
[self debug: @"%@ for cache-%@ query %@",
s, (YES == cacheHit) ? @"hit" : @"miss", stmt];
}
return result;
}

View file

@ -48,6 +48,7 @@ struct _SQLClientPoolItem {
@interface SQLClient(Pool)
- (void) _clearPool: (SQLClientPool*)p;
- (void) _waitPool: (NSTimeInterval)ti;
@end
@implementation SQLClient(Pool)
@ -57,6 +58,10 @@ struct _SQLClientPoolItem {
_pool = nil;
}
- (void) _waitPool: (NSTimeInterval)ti
{
_waitPool = ti;
}
@end
@interface SQLClientPool (Adjust)
@ -65,6 +70,10 @@ struct _SQLClientPoolItem {
@interface SQLClientPool (Private)
- (void) _lock;
- (SQLClient*) _provideClientBeforeDate: (NSDate*)when
exclusive: (BOOL)isLocal
blocked: (NSTimeInterval*)ti;
- (SQLClient*) _provide;
- (NSString*) _rc: (SQLClient*)o;
- (void) _unlock;
@end
@ -239,223 +248,9 @@ static Class cls = Nil;
- (SQLClient*) provideClientBeforeDate: (NSDate*)when exclusive: (BOOL)isLocal
{
NSThread *thread = [NSThread currentThread];
NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval now = start;
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)
{
[_lock lock];
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]);
}
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 (YES == [_lock tryLockWhenCondition: 1])
{
_immediate++;
}
else
{
NSTimeInterval end = [when timeIntervalSinceReferenceDate];
NSTimeInterval dif = 0.0;
NSDate *until;
BOOL locked;
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;
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]);
}
return client;
return [self _provideClientBeforeDate: when
exclusive: isLocal
blocked: (NSTimeInterval*)NULL];
}
- (SQLClient*) provideClientExclusive
@ -970,6 +765,259 @@ static Class cls = Nil;
[_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)
@ -1083,7 +1131,7 @@ static Class cls = Nil;
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db cache: seconds simpleQuery: query];
NS_HANDLER
@ -1101,7 +1149,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db cache: seconds query: stmt with: values];
NS_HANDLER
@ -1117,7 +1165,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db cache: seconds simpleQuery: stmt];
NS_HANDLER
@ -1136,7 +1184,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db cache: seconds
simpleQuery: stmt
@ -1165,7 +1213,7 @@ static Class cls = Nil;
va_start (ap, stmt);
info = [_items[0].c prepare: stmt args: ap];
va_end (ap);
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleExecute: info];
NS_HANDLER
@ -1181,7 +1229,7 @@ static Class cls = Nil;
SQLClient *db;
NSInteger result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db execute: stmt with: values];
NS_HANDLER
@ -1233,7 +1281,7 @@ static Class cls = Nil;
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleQuery: query];
NS_HANDLER
@ -1250,7 +1298,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db query: stmt with: values];
NS_HANDLER
@ -1273,7 +1321,7 @@ static Class cls = Nil;
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleQuery: query];
NS_HANDLER
@ -1308,7 +1356,7 @@ static Class cls = Nil;
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleQuery: query];
NS_HANDLER
@ -1422,7 +1470,7 @@ static Class cls = Nil;
SQLClient *db;
NSInteger result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleExecute: info];
NS_HANDLER
@ -1438,7 +1486,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleQuery: stmt];
NS_HANDLER
@ -1456,7 +1504,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleQuery: stmt
recordType: rtype