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> 2007-07-07 Richard Frith-Macdonald <rfm@gnu.org>
* SQLClient.m: Fix error causing loss of some debug output when an * SQLClient.m: Fix error causing loss of some debug output when an
exception occurs in a transaction. 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> 2007-04-01 Richard Frith-Macdonald <rfm@gnu.org>

106
JDBC.m
View file

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

View file

@ -77,6 +77,10 @@
Support for standalone web applications ... eg to allow data to be Support for standalone web applications ... eg to allow data to be
added to the database by people posting web forms to the application. added to the database by people posting web forms to the application.
</item> </item>
<item>
Supports notification of connection to and disconnection from the
database server.
</item>
</list> </list>
</section> </section>
<section> <section>
@ -187,6 +191,18 @@
@class NSString; @class NSString;
@class SQLTransaction; @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. * <p>An enhanced array to represent a record returned from a query.
* You should <em>NOT</em> try to create instances of this class * You should <em>NOT</em> try to create instances of this class
@ -971,6 +987,16 @@ extern unsigned SQLClientTimeTick();
*/ */
@interface SQLClient(Convenience) @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 * Executes a query (like the -query:,... method) and checks the result
* (raising an exception if the query did not contain a single record) * (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. * use the receiver as the database connection to perform transactions.
*/ */
- (SQLTransaction*) transaction; - (SQLTransaction*) transaction;
@end @end
@ -1154,11 +1181,13 @@ extern unsigned SQLClientTimeTick();
* database operations should be. If you have multiple threads, you * database operations should be. If you have multiple threads, you
* should create multiple SQLTransaction instances, at least one per thread. * should create multiple SQLTransaction instances, at least one per thread.
*/ */
@interface SQLTransaction : NSObject @interface SQLTransaction : NSObject <NSCopying>
{ {
SQLClient *_db; SQLClient *_db;
NSMutableArray *_info; NSMutableArray *_info;
unsigned _count; unsigned _count;
BOOL _batch;
BOOL _stop;
} }
/** /**
@ -1176,7 +1205,7 @@ extern unsigned SQLClientTimeTick();
- (void) add: (NSString*)stmt with: (NSDictionary*)values; - (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 * This provides a convenient way of merging transactions which have been
* built by different code modules, in order to have them all executed * built by different code modules, in order to have them all executed
* together in a single operation (for efficiency etc).<br /> * together in a single operation (for efficiency etc).<br />
@ -1188,6 +1217,11 @@ extern unsigned SQLClientTimeTick();
*/ */
- (void) append: (SQLTransaction*)other; - (void) append: (SQLTransaction*)other;
/**
* Make a copy of the receiver.
*/
- (id) copyWithZone: (NSZone*)z;
/** /**
* Returns the number of statements in this transaction. * Returns the number of statements in this transaction.
*/ */
@ -1218,6 +1252,24 @@ extern unsigned SQLClientTimeTick();
*/ */
- (void) execute; - (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. * Resets the transaction, removing all previously added statements.
* This allows the transaction object to be re-used for multiple * This allows the transaction object to be re-used for multiple

View file

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