Post notifications on connect and disconnect.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@25309 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
rfm 2007-07-09 17:08:22 +00:00
parent 3dea330f47
commit fb09bfd147
4 changed files with 296 additions and 121 deletions

View file

@ -1,7 +1,14 @@
2007-07-09 Richard Frith-Macdonald <rfm@gnu.org>
* SQLClient.m: Post notifications upon connect and disconnect.
2007-07-07 Richard Frith-Macdonald <rfm@gnu.org>
* SQLClient.m: Fix error causing loss of some debug output when an
exception occurs in a transaction.
Rewrite transaction code to support execution with automatic retry of
statements when batching.
* JDBC.m: Update for new transaction code
2007-04-01 Richard Frith-Macdonald <rfm@gnu.org>

106
JDBC.m
View file

@ -1675,64 +1675,39 @@ static int JDBCVARCHAR = 0;
@implementation _JDBCTransaction
// Marker for the end of data owned by a statement
static id marker = @"End of statement data";
- (NSString*) description
- (BOOL) _batchable: (NSArray*)a
{
return [NSString stringWithFormat: @"%@ with SQL '%@' for %@",
[super description],
(_count == 0 ? (id)@"" : (id)[_info objectAtIndex: 0]), _db];
unsigned c = [a count];
unsigned i;
for (i = 0; i < c; i++)
{
if ([[a objectAtIndex: i] count] > 1)
{
return NO;
}
}
return YES;
}
- (void) _addInfo: (NSArray*)info
- (void) _merge: (NSMutableArray*)a
{
if (_count == 0)
{
id o = [info objectAtIndex: 0];
NSMutableArray *ma;
unsigned c = [_info count];
unsigned i;
if ([o isKindOfClass: [NSString class]] == YES)
for (i = 0; i < c; i++)
{
id o = [_info objectAtIndex: i];
if ([o isKindOfClass: [NSArray class]] == YES)
{
ma = [[NSMutableArray alloc] initWithObjects: &o count: 1];
}
[a addObject: o];
}
else
{
ma = [(NSArray*)o mutableCopy];
}
[_info addObjectsFromArray: info];
[_info replaceObjectAtIndex: 0 withObject: ma];
RELEASE(ma);
[(_JDBCTransaction*)o _merge: a];
}
}
else
{
unsigned c = [info count];
unsigned i = 1;
id o = [info objectAtIndex: 0];
NSMutableArray *ma = [_info objectAtIndex: 0];
if ([o isKindOfClass: [NSString class]] == YES)
{
[ma addObject: (NSString*)o];
}
else
{
[ma addObjectsFromArray: (NSArray*)o];
}
while (i < c)
{
[_info addObject: [info objectAtIndex: i++]];
}
}
/* If the info item being added is a simple statement rather than the
* content of another transaction, we must add an end-of-statement marker.
*/
if ([info lastObject] != marker)
{
[_info addObject: marker];
}
_count++;
}
- (void) execute
@ -1768,13 +1743,16 @@ static id marker = @"End of statement data";
NS_DURING
{
NSMutableArray *statements = [_info objectAtIndex: 0];
unsigned numberOfStatements = [statements count];
NSMutableArray *statements;
unsigned numberOfStatements;
unsigned statement;
unsigned pos = 1;
NSTimeInterval _duration = [_db durationLogging];
NSTimeInterval start = 0.0;
statements = [NSMutableArray arrayWithCapacity: 100];
[self _merge: statements];
numberOfStatements = [statements count];
if (_duration >= 0)
{
start = GSTickerTimeNow();
@ -1786,15 +1764,12 @@ static id marker = @"End of statement data";
}
if (numberOfStatements > 1 && ji->addBatch != 0
&& [_info count] == numberOfStatements + 1)
&& [self _batchable: statements] == YES)
{
jintArray ja;
jint *array;
int status = 0;
/* We have multiple statements without arguments ... so this
* is batchable.
*/
for (statement = 0; statement < numberOfStatements; statement++)
{
NSString *stmt = [statements objectAtIndex: statement];
@ -1837,20 +1812,20 @@ static id marker = @"End of statement data";
*/
for (statement = 0; statement < numberOfStatements; statement++)
{
NSString *stmt = [statements objectAtIndex: statement];
NSArray *info = [statements objectAtIndex: statement];
NSString *stmt = [info objectAtIndex: 0];
unsigned c = [info count];
jmethodID jm;
jobject js;
if ([_info objectAtIndex: pos] == marker)
if (c == 1)
{
pos++; // Step past end of statement data.
(*env)->CallIntMethod (env, ji->statement,
ji->executeUpdate, JStringFromNSString(env, stmt));
}
else
{
NSData *data;
int i = 1;
unsigned i;
jclass jc;
stmt = [stmt stringByReplacingString: @"'?'''?'"
@ -1868,9 +1843,12 @@ static id marker = @"End of statement data";
/* Get data arguments for statement.
*/
while ((data = [_info objectAtIndex: pos++]) != marker)
{
(*env)->CallIntMethod (env, js, jm, i++,
for (i = 1; i < c; i++)
{
NSData *data;
data = [info objectAtIndex: i];
(*env)->CallIntMethod (env, js, jm, i,
ByteArrayFromNSData(env, data));
JException(env);
}

View file

@ -77,6 +77,10 @@
Support for standalone web applications ... eg to allow data to be
added to the database by people posting web forms to the application.
</item>
<item>
Supports notification of connection to and disconnection from the
database server.
</item>
</list>
</section>
<section>
@ -187,6 +191,18 @@
@class NSString;
@class SQLTransaction;
/**
* Notification sent when an instance becomes connected to the database
* server. The notification object is the instance connected.
*/
NSString *SQLClientDidConnectNotification;
/**
* Notification sent when an instance becomes disconnected from the database
* server. The notification object is the instance disconnected.
*/
NSString *SQLClientDidDisconnectNotification;
/**
* <p>An enhanced array to represent a record returned from a query.
* You should <em>NOT</em> try to create instances of this class
@ -971,6 +987,16 @@ extern unsigned SQLClientTimeTick();
*/
@interface SQLClient(Convenience)
/**
* Returns a transaction object configured to handle batching and
* execute part of a batch of statements if execution of the whole
* fails.<br />
* If stopOnFailure is YES than execution of the transaction will
* stop with the first statement to fail, otherwise it will execute
* all the statements it can, skipping any failued statements.
*/
- (SQLTransaction*) batch: (BOOL)stopOnFailure;
/**
* Executes a query (like the -query:,... method) and checks the result
* (raising an exception if the query did not contain a single record)
@ -998,6 +1024,7 @@ extern unsigned SQLClientTimeTick();
* use the receiver as the database connection to perform transactions.
*/
- (SQLTransaction*) transaction;
@end
@ -1154,11 +1181,13 @@ extern unsigned SQLClientTimeTick();
* database operations should be. If you have multiple threads, you
* should create multiple SQLTransaction instances, at least one per thread.
*/
@interface SQLTransaction : NSObject
@interface SQLTransaction : NSObject <NSCopying>
{
SQLClient *_db;
NSMutableArray *_info;
unsigned _count;
BOOL _batch;
BOOL _stop;
}
/**
@ -1176,7 +1205,7 @@ extern unsigned SQLClientTimeTick();
- (void) add: (NSString*)stmt with: (NSDictionary*)values;
/**
* Appends all the statements from the other transaction to the receiver.<br />
* Appends a copy of the other transaction to the receiver.<br />
* This provides a convenient way of merging transactions which have been
* built by different code modules, in order to have them all executed
* together in a single operation (for efficiency etc).<br />
@ -1188,6 +1217,11 @@ extern unsigned SQLClientTimeTick();
*/
- (void) append: (SQLTransaction*)other;
/**
* Make a copy of the receiver.
*/
- (id) copyWithZone: (NSZone*)z;
/**
* Returns the number of statements in this transaction.
*/
@ -1218,6 +1252,24 @@ extern unsigned SQLClientTimeTick();
*/
- (void) execute;
/**
* <p>This is similar to the -execute method, but may allow partial
* execution of the transaction if appropriate:
* </p>
* <p>If the transaction was created using the [SQLClient-batch:] method and
* the transaction as a whole fails, individual statements are retried.<br />
* The stopOnFailure flag for the batch creation indicates whether the
* retries are stopped at the first statement to fail, or continue (skipping
* any failed statements).
* </p>
* <p>If the transaction has had transactions appended to it, those
* subsidiary transactions may succeed or fail atomically depending
* on their individual attributes.
* </p>
* The method returns the number of statements which actually succeeded.
*/
- (unsigned) executeBatch;
/**
* Resets the transaction, removing all previously added statements.
* This allows the transaction object to be re-used for multiple

View file

@ -63,6 +63,12 @@
#define SUBCLASS_RESPONSIBILITY
#endif
NSString *SQLClientDidConnectNotification
= @"SQLClientDidConnectNotification";
NSString *SQLClientDidDisconnectNotification
= @"SQLClientDidDisconnectNotification";
static NSNull *null = nil;
static Class NSStringClass = 0;
static Class NSArrayClass = 0;
@ -1110,6 +1116,12 @@ static unsigned int maxConnections = 8;
NS_ENDHANDLER
}
[lock unlock];
if (connected == YES)
{
[[NSNotificationCenter defaultCenter]
postNotificationName: SQLClientDidConnectNotification
object: self];
}
}
return connected;
}
@ -1190,6 +1202,9 @@ static unsigned int maxConnections = 8;
NS_ENDHANDLER
}
[lock unlock];
[[NSNotificationCenter defaultCenter]
postNotificationName: SQLClientDidDisconnectNotification
object: self];
}
}
@ -1659,6 +1674,7 @@ static unsigned int maxConnections = 8;
[lock lock];
statement = [info objectAtIndex: 0];
if ([statement isEqualToString: commitString])
{
isCommit = YES;
@ -2292,6 +2308,20 @@ static unsigned int maxConnections = 8;
@implementation SQLClient(Convenience)
- (SQLTransaction*) batch: (BOOL)stopOnFailure
{
TDefs transaction;
transaction = (TDefs)NSAllocateObject([SQLTransaction class], 0,
NSDefaultMallocZone());
transaction->_db = RETAIN(self);
transaction->_info = [NSMutableArray new];
transaction->_batch = YES;
transaction->_stop = stopOnFailure;
return AUTORELEASE((SQLTransaction*)transaction);
}
- (SQLRecord*) queryRecord: (NSString*)stmt, ...
{
va_list ap;
@ -2539,101 +2569,209 @@ static unsigned int maxConnections = 8;
(_count == 0 ? (id)@"" : (id)[_info objectAtIndex: 0]), _db];
}
- (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]];
[_info addObject: [_db _prepare: stmt args: ap]];
_count++;
va_end (ap);
}
- (void) add: (NSString*)stmt with: (NSDictionary*)values
{
[self _addInfo: [_db _substitute: stmt with: values]];
[_info addObject: [_db _substitute: stmt with: values]];
_count++;
}
- (void) append: (SQLTransaction*)other
{
if (other != nil && other->_count > 0)
{
[self _addInfo: other->_info];
other = [other copy];
[_info addObject: other];
_count += other->_count;
RELEASE(other);
}
}
- (id) copyWithZone: (NSZone*)z
{
SQLTransaction *c;
c = (SQLTransaction*)NSCopyObject(self, 0, z);
c->_info = [c->_info mutableCopy];
return c;
}
- (SQLClient*) db
{
return _db;
}
- (void) _addSQL: (NSMutableString*)sql andArgs: (NSMutableArray*)args
{
unsigned count = [_info count];
unsigned index;
for (index = 0; index < count; index++)
{
id o = [_info objectAtIndex: index];
if ([o isKindOfClass: NSArrayClass] == YES)
{
unsigned c = [(NSArray*)o count];
if (c > 0)
{
unsigned i;
[sql appendString: [(NSArray*)o objectAtIndex: 0]];
[sql appendString: @";"];
for (i = 1; i < c; i++)
{
[args addObject: [(NSArray*)o objectAtIndex: i]];
}
}
}
else
{
[(SQLTransaction*)o _addSQL: sql andArgs: args];
}
}
}
- (void) _countLength: (unsigned*)length andArgs: (unsigned*)args
{
unsigned count = [_info count];
unsigned index;
for (index = 0; index < count; index++)
{
id o = [_info objectAtIndex: index];
if ([o isKindOfClass: NSArrayClass] == YES)
{
unsigned c = [(NSArray*)o count];
if (c > 0)
{
length += [[(NSArray*)o objectAtIndex: 0] length] + 1;
args += c - 1;
}
}
else
{
[(SQLTransaction*)o _countLength: length andArgs: args];
}
}
}
- (void) execute
{
if (_count > 0)
{
BOOL wrapped = NO;
NSMutableString *sql = [_info objectAtIndex: 0];
NSMutableArray *info = nil;
NS_DURING
{
if (_count > 1 && [_db isInTransaction] == NO)
{
wrapped = YES;
[sql replaceCharactersInRange: NSMakeRange(0, 0)
withString: @"begin;"];
[sql replaceCharactersInRange: NSMakeRange([sql length], 0)
withString: @";commit"];
}
[_db simpleExecute: _info];
if (wrapped == YES)
{
wrapped = NO;
[sql replaceCharactersInRange: NSMakeRange([sql length] - 7, 7)
withString: @""];
[sql replaceCharactersInRange: NSMakeRange(0, 6)
withString: @""];
}
NSMutableString *sql;
unsigned sqlSize = 0;
unsigned argCount = 0;
[self _countLength: &sqlSize andArgs: &argCount];
/* Allocate and initialise the transaction statement.
*/
info = [[NSMutableArray alloc] initWithCapacity: argCount + 1];
sql = [[NSMutableString alloc] initWithCapacity: sqlSize + 13];
[info addObject: sql];
RELEASE(sql);
if ([_db isInTransaction] == NO)
{
[sql appendString: @"begin;"];
}
[self _addSQL: sql andArgs: info];
if ([_db isInTransaction] == NO)
{
[sql appendString: @"commit;"];
}
[_db simpleExecute: info];
}
NS_HANDLER
{
if (wrapped == YES)
{
[sql replaceCharactersInRange: NSMakeRange([sql length] - 7, 7)
withString: @""];
[sql replaceCharactersInRange: NSMakeRange(0, 6)
withString: @""];
}
RELEASE(info);
[localException raise];
}
NS_ENDHANDLER
}
}
- (unsigned) executeBatch
{
unsigned executed = 0;
if (_count > 0)
{
NS_DURING
{
[self execute];
executed = _count;
}
NS_HANDLER
{
if (_batch == YES)
{
unsigned count = [_info count];
unsigned i;
for (i = 0; i < count; i++)
{
BOOL success = NO;
NS_DURING
{
id o = [_info objectAtIndex: i];
if ([o isKindOfClass: NSArrayClass] == YES)
{
[_db simpleExecute: (NSArray*)o];
executed++;
success = YES;
}
else
{
unsigned result;
result = [(SQLTransaction*)o executeBatch];
executed += result;
if (result == [(SQLTransaction*)o count])
{
success = YES;
}
}
}
NS_HANDLER
{
success = NO;
}
NS_ENDHANDLER
if (success == NO && _stop == YES)
{
break;
}
}
}
}
NS_ENDHANDLER
}
return executed;
}
- (void) reset
{
[_info removeAllObjects];