first attempt at merge code

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@38041 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
rfm 2014-08-08 08:07:06 +00:00
parent f537fcc33d
commit ff5a0b347f
3 changed files with 222 additions and 5 deletions

View file

@ -1,3 +1,9 @@
2014-08-08 Richard Frith-Macdonald <rfm@gnu.org>
* SQLClient.h:
* SQLClient.m:
Add merging of insert/update statements in a transaction.
2014-07-17 Yavor Doganov <yavor@gnu.org>
Install bundles in a versioned directory.

View file

@ -1572,7 +1572,7 @@ SQLCLIENT_PRIVATE
- (id) copyWithZone: (NSZone*)z;
/**
* Returns the number of individual statements ond/r subsidiary transactions
* Returns the number of individual statements and/or subsidiary transactions
* which have been added to the receiver. For a count of the total number
* of statements, use the -totalCount method.
*/
@ -1649,6 +1649,40 @@ SQLCLIENT_PRIVATE
*/
- (void) insertTransaction: (SQLTransaction*)trn atIndex: (unsigned)index;
/**
* Like -add:... but, if the new statement can be merged with a recently
* added one, this does that rather than adding as a separate statement.
* <p>You may use this with an insert statement of the form:<br />
* INSERT INTO table (fieldnames) VALUES (values);<br />
* For databases which support multiline inserts such that they can be
* merged into something of the form:
* INSERT INTO table (fieldnames) VALUES (values1),(values2),...;
* </p>
* <p>Or may use this with an update statement of the form:<br />
* UPDATE table SET settings WHERE condition;<br />
* So that statements may be merged into:<br />
* UPDATE table SET setting WHERE (condition1) OR (condition2) OR ...;
* </p>
* If no opportunity for merging is found, the new statement is simply
* added to the transaction.<br />
* Caveats:<br />
* 1. databases may not actually support multiline insert.<br />
* 2. Only the most recent five statements in a transaction are checked
* for eligibility.<br />
* 3. Merging is done only if the statement up to the string 'VALUES'
* (for insert) or 'WHERE' (for update) matches.<br />
* 4. This is a simple text match rather than sql syntactic analysis,
* so it's possible to confuse the process with complex statements.
*/
- (void) merge: (NSString*)stmt,...;
/**
* Like -add:with: but, if the new statement can be merged with a recently
* added one, this does that rather than adding as a separate statement.
* See -merge:,... for meore details.
*/
- (void) merge: (NSString*)stmt with: (NSDictionary*)values;
/** Remove the index'th transaction or statement from the receiver.
*/
- (void) removeTransactionAtIndex: (unsigned)index;

View file

@ -650,9 +650,9 @@ static NSArray *rollbackStatement = nil;
@interface SQLClient (Private)
- (void) _configure: (NSNotification*)n;
- (void) _populateCache: (CacheQuery*)a;
- (NSArray*) _prepare: (NSString*)stmt args: (va_list)args;
- (NSMutableArray*) _prepare: (NSString*)stmt args: (va_list)args;
- (void) _recordMainThread;
- (NSArray*) _substitute: (NSString*)str with: (NSDictionary*)vals;
- (NSMutableArray*) _substitute: (NSString*)str with: (NSDictionary*)vals;
+ (void) _tick: (NSTimer*)t;
@end
@ -2236,7 +2236,7 @@ static unsigned int maxConnections = 8;
* any NSData objects following. The NSData objects appear in the
* statement strings as the marker sequence - <code>'?'''?'</code>
*/
- (NSArray*) _prepare: (NSString*)stmt args: (va_list)args
- (NSMutableArray*) _prepare: (NSString*)stmt args: (va_list)args
{
NSMutableArray *ma = [NSMutableArray arrayWithCapacity: 2];
NSString *tmp = va_arg(args, NSString*);
@ -2290,7 +2290,7 @@ static unsigned int maxConnections = 8;
* any NSData objects following. The NSData objects appear in the
* statement strings as the marker sequence - <code>'?'''?'</code>
*/
- (NSArray*) _substitute: (NSString*)str with: (NSDictionary*)vals
- (NSMutableArray*) _substitute: (NSString*)str with: (NSDictionary*)vals
{
unsigned int l = [str length];
NSRange r;
@ -3202,6 +3202,183 @@ static unsigned int maxConnections = 8;
[trn release];
}
/* Try to merge the prepared statement p with an earlier statement in the
* transaction. We search up to 5 earlier statements and we merge if;
* a. We have something like 'INSERT INTO table (fields) VALUES (values)'
* where everything up to 'VALUES' is the same, so we can built a multiline
* insert like 'INSERT INTO table (fields) VALUES (values1),(values2),...'
* b. We have something like 'UPDATE table SET settings WHERE condition'
* where everything up to the condition is the same, so we can build
* 'UPDATE table SET settings WHERE condition1 OR (condition2) OR ...'
*/
- (void) _merge: (NSMutableArray*)p
{
if (_count > 0)
{
static NSCharacterSet *w = nil;
NSString *s;
NSRange r;
s = [p objectAtIndex: 0]; // Get SQL part of array
if (nil == w)
{
w = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain];
}
r = [s rangeOfString: @"INSERT" options: NSCaseInsensitiveSearch];
if (r.length > 0 && 0 == r.location)
{
r = [s rangeOfString: @"VALUES" options: NSCaseInsensitiveSearch];
if (r.length > 0)
{
NSUInteger l = [s length];
NSUInteger pos = NSMaxRange(r);
while (pos < l
&& [w characterIsMember: [s characterAtIndex: pos]])
{
pos++;
}
if (pos < l && [s characterAtIndex: pos] == '(')
{
NSString *t = [s substringToIndex: pos];
NSUInteger index = _count;
NSUInteger attempts = 0;
s = [s substringFromIndex: pos];
while (index-- > 0 && attempts++ < 5)
{
NSMutableArray *o;
NSString *os;
o = [_info objectAtIndex: index];
os = [o objectAtIndex: 0];
if ([os hasPrefix: t])
{
NSMutableString *m;
if ([os isKindOfClass: [NSMutableString class]])
{
m = (NSMutableString*)os;
}
else
{
m = [[os mutableCopy] autorelease];
}
[m appendString: @","];
[m appendString: s];
[o replaceObjectAtIndex: 0 withObject: m];
for (index = 1; index < [p count]; index++)
{
[o addObject: [p objectAtIndex: index]];
}
return;
}
}
}
}
}
r = [s rangeOfString: @"UPDATE" options: NSCaseInsensitiveSearch];
if (r.length > 0 && 0 == r.location)
{
r = [s rangeOfString: @"WHERE" options: NSCaseInsensitiveSearch];
if (r.length > 0)
{
NSUInteger l = [s length];
NSUInteger pos = NSMaxRange(r);
while (pos < l
&& [w characterIsMember: [s characterAtIndex: pos]])
{
pos++;
}
if (pos < l && [s characterAtIndex: pos] == '(')
{
NSString *t = [s substringToIndex: pos];
NSUInteger index = _count;
NSUInteger attempts = 0;
/* Get the condition after the WHERE and if it's not
* in brackets, add them so the merge can work.
*/
s = [s substringFromIndex: pos];
if ([s characterAtIndex: 0] != '(')
{
s = [NSString stringWithFormat: @"(%@)", s];
}
while (index-- > 0 && attempts++ < 5)
{
NSMutableArray *o;
NSString *os;
o = [_info objectAtIndex: index];
os = [o objectAtIndex: 0];
if ([os hasPrefix: t])
{
NSMutableString *m;
l = [os length];
if ([os characterAtIndex: l - 1] == ')')
{
if ([os isKindOfClass: [NSMutableString class]])
{
m = (NSMutableString*)os;
}
else
{
m = [[os mutableCopy] autorelease];
}
}
else
{
/* The condition of the WHERE clause was not
* bracketed, so we extract it and build a
* new statement in which it is bracketed.
*/
os = [os substringFromIndex: pos];
m = [NSMutableString stringWithFormat:
@"%@(%@)", t, os];
}
[m appendString: @" OR "];
[m appendString: s];
[o replaceObjectAtIndex: 0 withObject: m];
for (index = 1; index < [p count]; index++)
{
[o addObject: [p objectAtIndex: index]];
}
return;
}
}
}
}
}
}
[_info addObject: p];
_count++;
}
- (void) merge: (NSString*)stmt,...
{
va_list ap;
NSMutableArray *p;
va_start (ap, stmt);
p = [_db _prepare: stmt args: ap];
va_end (ap);
[self _merge: p];
}
- (void) merge: (NSString*)stmt with: (NSDictionary*)values
{
NSMutableArray *p;
p = [_db _substitute: stmt with: values];
[self _merge: p];
}
- (void) removeTransactionAtIndex: (unsigned)index
{
id o;