SQLRecord enhancements for KVC and for performance optimisations.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@24827 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
rfm 2007-03-08 17:12:55 +00:00
parent dd2ac4d9e6
commit 420fe259a8
9 changed files with 465 additions and 52 deletions

View file

@ -1,3 +1,20 @@
2007-03-08 Richard Frith-Macdonald <rfm@gnu.org>
* SQLClient.h:
* SQLClient.m:
* MySQL.m:
* ECPG.pgm:
* Postgres.m:
* Oracle.pm:
* SQLite.m:
* JDBC.m:
Add KVC support for SQLRecord. Make SQLRecord into a class cluster
with a single concrete implementation for now. Extend API to allow
specifying of an alternative SQLRecord subclass when doing a query
so that query results can be efficiently stored into custom subclasses
rather than having to first be retrieved into an SQLRecord and then
copied.
2007-02-14 Nicola Pero <nicola.pero@meta-innovation.com>
* GNUmakefile (BUNDLE_INSTALL_DIR): Set using GNUSTEP_BUNDLES,

View file

@ -355,7 +355,7 @@ static unsigned int trim(char *str)
return (str - start);
}
- (NSMutableArray*) backendQuery: (NSString*)stmt
- (NSMutableArray*) backendQuery: (NSString*)stmt recordClass: (Class)rClass
{
EXEC SQL BEGIN DECLARE SECTION;
bool aBool;
@ -597,9 +597,9 @@ static unsigned int trim(char *str)
keys[index-1] = [NSString stringWithUTF8String:
fieldName];
}
record = [SQLRecord newWithValues: values
keys: keys
count: count];
record = [rClass newWithValues: values
keys: keys
count: count];
[records addObject: record];
RELEASE(record);
}

8
JDBC.m
View file

@ -1241,7 +1241,7 @@ static int JDBCVARCHAR = 0;
DESTROY(arp);
}
- (NSMutableArray*) backendQuery: (NSString*)stmt
- (NSMutableArray*) backendQuery: (NSString*)stmt recordClass: (Class)rClass
{
NSMutableArray *records = nil;
CREATE_AUTORELEASE_POOL(arp);
@ -1474,9 +1474,9 @@ static int JDBCVARCHAR = 0;
[localException raise];
}
NS_ENDHANDLER
record = [SQLRecord newWithValues: values
keys: keys
count: fieldCount];
record = [rClass newWithValues: values
keys: keys
count: fieldCount];
[records addObject: record];
RELEASE(record);
}

View file

@ -266,7 +266,7 @@ static unsigned int trim(char *str)
return (str - start);
}
- (NSMutableArray*) backendQuery: (NSString*)stmt
- (NSMutableArray*) backendQuery: (NSString*)stmt recordClass: (Class)rClass
{
CREATE_AUTORELEASE_POOL(arp);
NSMutableArray *records = nil;
@ -412,9 +412,9 @@ static unsigned int trim(char *str)
}
values[j] = v;
}
record = [SQLRecord newWithValues: values
keys: keys
count: fieldCount];
record = [rClass newWithValues: values
keys: keys
count: fieldCount];
[records addObject: record];
RELEASE(record);
}

View file

@ -326,7 +326,7 @@ static unsigned int trim(char *str)
return (str - start);
}
- (NSMutableArray*) backendQuery: (NSString*)stmt
- (NSMutableArray*) backendQuery: (NSString*)stmt recordClass: (Class)rClass
{
EXEC SQL BEGIN DECLARE SECTION;
int count;
@ -597,9 +597,9 @@ static unsigned int trim(char *str)
keys[index - 1] = [NSString stringWithUTF8String:
fieldName];
}
record = [SQLRecord newWithValues: values
keys: keys
count: count];
record = [rClass newWithValues: values
keys: keys
count: count];
[records addObject: record];
RELEASE(record);
}

View file

@ -363,7 +363,7 @@ static unsigned int trim(char *str)
return (str - start);
}
- (NSMutableArray*) backendQuery: (NSString*)stmt
- (NSMutableArray*) backendQuery: (NSString*)stmt recordClass: (Class)rClass
{
CREATE_AUTORELEASE_POOL(arp);
PGresult *result = 0;
@ -490,9 +490,9 @@ static unsigned int trim(char *str)
}
values[j] = v;
}
record = [SQLRecord newWithValues: values
keys: keys
count: fieldCount];
record = [rClass newWithValues: values
keys: keys
count: fieldCount];
[records addObject: record];
RELEASE(record);
}

View file

@ -188,15 +188,17 @@
@class SQLTransaction;
/**
* 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
* except via the +newWithValues:keys:count: method.
* </p>
* <p>NB. SQLRecord is the abstract base class of a class cluster.
* If you wish to subclass it you must implement the primitive methods
* +newWithValues:keys:count: -count -keyAtIndex: -objectAtIndex:
* and -replaceObjectAtIndex:withObject:
* </p>
*/
@interface SQLRecord : NSArray
{
@private
unsigned int count;
}
/**
* Create a new SQLRecord containing the specified fields.<br />
@ -212,23 +214,50 @@
*/
- (NSArray*) allKeys;
/**
* Returns the number of items in the record.<br />
* Subclasses must implement this method.
*/
- (unsigned) count;
/**
* Return the record as a mutable dictionary with the keys as the
* record field names standardised to be lowercase strings.
*/
- (NSMutableDictionary*) dictionary;
/**
* Optimised mechanism for retrieving all keys in order.
*/
- (void) getKeys: (id*)buf;
/**
* Optimised mechanism for retrieving all objects.
*/
- (void) getObjects: (id*)buf;
/** <override-subclass />
* Returns the key at the specified indes.<br />
*/
- (NSString*) keyAtIndex: (unsigned)index;
/** <override-subclass />
* Returns the object at the specified indes.<br />
*/
- (id) objectAtIndex: (unsigned)index;
/**
* Returns the value of the named field.<br />
* The field name is case insensitive.
*/
- (id) objectForKey: (NSString*)key;
/**
* Replaces the value at the specified index.<br />
* Subclasses must implement this method.
*/
- (void) replaceObjectAtIndex: (unsigned)index withObject: (id)anObject;
/**
* Replaces the value of the named field.<br />
* The field name is case insensitive.<br />
@ -400,6 +429,49 @@ extern unsigned SQLClientTimeTick();
*/
- (void) begin;
/**
* <p>Build an sql query string using the supplied arguments.
* </p>
* <p>This method has at least one argument, the string starting the
* query to be executed (which must have the prefix 'select ').
* </p>
* <p>Additional arguments are a nil terminated list which also be strings,
* and these are appended to the statement.<br />
* Any string arguments are assumed to have been quoted appropriately
* already, but non-string arguments are automatically quoted using the
* -quote: method.
* </p>
* <example>
* sql = [db buildQuery: @"SELECT Name FROM ", table, nil];
* </example>
* <p>Upon error, an exception is raised.
* </p>
* <p>The method returns a string containing sql suitable for passing to
* the -simpleQuery:recordClass: or -cache:simpleQuery:recordClass: methods.
* </p>
*/
- (NSString*) buildQuery: (NSString*)stmt,...;
/**
* Takes the query statement and substitutes in values from
* the dictionary where markup of the format {key} is found.<br />
* Returns the resulting query string.
* <example>
* sql = [db buildQuery: @"SELECT Name FROM {Table} WHERE ID = {ID}"
* with: values];
* </example>
* <p>Any non-string values in the dictionary will be replaced by
* the results of the -quote: method.<br />
* The markup format may also be {key?default} where <em>default</em>
* is a string to be used if there is no value for the <em>key</em>
* in the dictionary.
* </p>
* <p>The method returns a string containing sql suitable for passing to
* the -simpleQuery:recordClass: or -cache:simpleQuery:recordClass: methods.
* </p>
*/
- (NSString*) buildQuery: (NSString*)stmt with: (NSDictionary*)values;
/**
* Return the client name for this instance.<br />
* Normally this is useful only for debugging/reporting purposes, but
@ -548,14 +620,8 @@ extern unsigned SQLClientTimeTick();
/**
* <p>Perform arbitrary query <em>which returns values.</em>
* </p>
* <p>This method has at least one argument, the string starting the
* statement to be executed (which must have the prefix 'select ').
* </p>
* <p>Additional arguments are a nil terminated list which also be strings,
* and these are appended to the statement.<br />
* Any string arguments are assumed to have been quoted appropriately
* already, but non-string arguments are automatically quoted using the
* -quote: method.
* <p>This method handles its arguments in the same way as the -buildQuery:,...
* method and returns the result of the query.
* </p>
* <example>
* result = [db query: @"SELECT Name FROM ", table, nil];
@ -579,8 +645,8 @@ extern unsigned SQLClientTimeTick();
/**
* Takes the query statement and substitutes in values from
* the dictionary where markup of the format {key} is found.<br />
* Passes the result to the -query:,... method to execute.
* the dictionary (in the same manner as the -buildQuery:with: method)
* then executes the query and returns the response.<br />
* <example>
* result = [db query: @"SELECT Name FROM {Table} WHERE ID = {ID}"
* with: values];
@ -702,11 +768,16 @@ extern unsigned SQLClientTimeTick();
- (void) simpleExecute: (NSArray*)info;
/**
* Calls -backendQuery: in a safe manner.<br />
* Calls -simpleQuery:recordClass: with the default record class.
*/
- (NSMutableArray*) simpleQuery: (NSString*)stmt;
/**
* Calls -backendQuery:recordClass: in a safe manner.<br />
* Handles locking.<br />
* Maintains -lastOperation date.
*/
- (NSMutableArray*) simpleQuery: (NSString*)stmt;
- (NSMutableArray*) simpleQuery: (NSString*)stmt recordClass: (Class)cls;
/**
* Return the database user for this instance (or nil).
@ -802,7 +873,8 @@ extern unsigned SQLClientTimeTick();
* <p>Perform arbitrary query <em>which returns values.</em>
* </p>
* <example>
* result = [db backendQuery: @"SELECT Name FROM Table"];
* result = [db backendQuery: @"SELECT Name FROM Table"
* recordClass: [SQLRecord class]];
* </example>
* <p>Upon error, an exception is raised.
* </p>
@ -821,6 +893,16 @@ extern unsigned SQLClientTimeTick();
* <p>Application code must <em>not</em> call this method directly, it is
* for internal use only.
* </p>
* <p>The cls argument specifies a subclass of [SQLRecord] to be used to
* create the records produced by the query.<br />
* This is provided as a performance optimisation when you want to store
* data directly into a special class of your own.
* </p>
*/
- (NSMutableArray*) backendQuery: (NSString*)stmt recordClass: (Class)cls;
/**
* Calls -backendQuery:recordClass: with the default record class.
*/
- (NSMutableArray*) backendQuery: (NSString*)stmt;
@ -1015,6 +1097,11 @@ extern unsigned SQLClientTimeTick();
query: (NSString*)stmt
with: (NSDictionary*)values;
/**
* Calls -cache:simpleQuery:recordClass: with the default record class.
*/
- (NSMutableArray*) cache: (int)seconds simpleQuery: (NSString*)stmt;
/**
* If the result of the query is already cached and is still valid,
* return it. Otherwise, perform the query and cache the result
@ -1026,7 +1113,9 @@ extern unsigned SQLClientTimeTick();
* Handles locking.<br />
* Maintains -lastOperation date.
*/
- (NSMutableArray*) cache: (int)seconds simpleQuery: (NSString*)stmt;
- (NSMutableArray*) cache: (int)seconds
simpleQuery: (NSString*)stmt
recordClass: (Class)cls;
/**
* Sets the cache to be used by the receiver for storing the results of

View file

@ -285,6 +285,9 @@ typedef struct {
@defs(SQLTransaction);
} *TDefs;
@class _SQLRecord;
static Class rClass = 0;
@implementation SQLRecord
+ (id) allocWithZone: (NSZone*)aZone
{
@ -298,16 +301,226 @@ typedef struct {
if (null == nil)
{
null = [NSNull new];
rClass = [_SQLRecord class];
}
}
+ (id) newWithValues: (id*)v keys: (NSString**)k count: (unsigned int)c
{
return [rClass newWithValues: v keys: k count: c];
}
- (NSArray*) allKeys
{
unsigned count = [self count];
id buf[count];
while (count-- > 0)
{
buf[count] = [self keyAtIndex: count];
}
return [NSArray arrayWithObjects: buf count: count];
}
- (id) copyWithZone: (NSZone*)z
{
return RETAIN(self);
}
- (unsigned int) count
{
[self subclassResponsibility: _cmd];
return 0;
}
- (NSMutableDictionary*) dictionary
{
unsigned count = [self count];
id keys[count];
id vals[count];
[self getKeys: keys];
[self getObjects: vals];
return [NSMutableDictionary dictionaryWithObjects: vals
forKeys: keys
count: count];
}
- (void) getKeys: (id*)buf
{
unsigned i = [self count];
while (i-- > 0)
{
buf[i] = [self keyAtIndex: i];
}
}
- (void) getObjects: (id*)buf
{
unsigned i = [self count];
while (i-- > 0)
{
buf[i] = [self objectAtIndex: i];
}
}
- (id) init
{
NSLog(@"Illegal attempt to -init an SQLRecord");
DESTROY(self);
return self;
}
- (NSString*) keyAtIndex: (unsigned int)pos
{
return [self subclassResponsibility: _cmd];
}
- (id) objectAtIndex: (unsigned int)pos
{
return [self subclassResponsibility: _cmd];
}
- (id) objectForKey: (NSString*)key
{
unsigned count = [self count];
unsigned pos;
id keys[count];
[self getKeys: keys];
for (pos = 0; pos < count; pos++)
{
if ([key isEqualToString: keys[pos]] == YES)
{
break;
}
}
if (pos == count)
{
for (pos = 0; pos < count; pos++)
{
if ([key caseInsensitiveCompare: keys[pos]] == NSOrderedSame)
{
break;
}
}
}
if (pos == count)
{
return nil;
}
else
{
return [self objectAtIndex: pos];
}
}
- (void) replaceObjectAtIndex: (unsigned)index withObject: (id)anObject
{
[self subclassResponsibility: _cmd];
}
- (void) setObject: (id)anObject forKey: (NSString*)aKey
{
unsigned count = [self count];
unsigned pos;
id keys[count];
if (anObject == nil)
{
anObject = null;
}
[self getKeys: keys];
for (pos = 0; pos < count; pos++)
{
if ([aKey isEqualToString: keys[pos]] == YES)
{
break;
}
}
if (pos == count)
{
for (pos = 0; pos < count; pos++)
{
if ([aKey caseInsensitiveCompare: keys[pos]] == NSOrderedSame)
{
break;
}
}
}
if (pos == count)
{
[NSException raise: NSInvalidArgumentException
format: @"Bad key (%@) in -setObject:forKey:", aKey];
}
else
{
[self replaceObjectAtIndex: pos withObject: anObject];
}
}
- (unsigned) sizeInBytes: (NSMutableSet*)exclude
{
if ([exclude member: self] != nil)
{
return 0;
}
else
{
unsigned size = [super sizeInBytes: exclude];
unsigned pos;
unsigned count = [self count];
id vals[count];
[self getObjects: vals];
for (pos = 0; pos < count; pos++)
{
size += [vals[pos] sizeInBytes: exclude];
}
return size;
}
}
@end
@implementation SQLRecord (KVC)
- (void) setValue: (id)aValue forKey: (NSString*)aKey
{
[self setObject: aValue forKey: aKey];
}
- (id) valueForKey: (NSString*)aKey
{
id v = [self objectForKey: aKey];
if (v == nil)
{
v = [super valueForKey: aKey];
}
return v;
}
@end
@interface _SQLRecord : SQLRecord
{
unsigned count;
}
@end
@implementation _SQLRecord
+ (id) newWithValues: (id*)v keys: (NSString**)k count: (unsigned int)c
{
id *ptr;
SQLRecord *r;
_SQLRecord *r;
unsigned pos;
r = (SQLRecord*)NSAllocateObject(self, c*2*sizeof(id), NSDefaultMallocZone());
r = (_SQLRecord*)NSAllocateObject(self,
c*2*sizeof(id), NSDefaultMallocZone());
r->count = c;
ptr = ((void*)&(r->count)) + sizeof(r->count);
for (pos = 0; pos < c; pos++)
@ -372,6 +585,19 @@ typedef struct {
return d;
}
- (void) getKeys: (id*)buf
{
id *ptr;
unsigned pos;
ptr = ((void*)&count) + sizeof(count);
ptr += count; // Step past objects to keys.
for (pos = 0; pos < count; pos++)
{
buf[pos] = ptr[pos];
}
}
- (void) getObjects: (id*)buf
{
id *ptr;
@ -391,6 +617,20 @@ typedef struct {
return self;
}
- (id) keyAtIndex: (unsigned int)pos
{
id *ptr;
if (pos >= count)
{
[NSException raise: NSRangeException
format: @"Array index too large"];
}
ptr = ((void*)&count) + sizeof(count);
ptr += count;
return ptr[pos];
}
- (id) objectAtIndex: (unsigned int)pos
{
id *ptr;
@ -427,6 +667,24 @@ typedef struct {
return nil;
}
- (void) replaceObjectAtIndex: (unsigned)index withObject: (id)anObject
{
id *ptr;
if (index >= count)
{
[NSException raise: NSRangeException
format: @"Array index too large"];
}
if (anObject == nil)
{
anObject = null;
}
ptr = ((void*)&count) + sizeof(count);
ptr += index;
ASSIGN(*ptr, anObject);
}
- (void) setObject: (id)anObject forKey: (NSString*)aKey
{
id *ptr;
@ -643,10 +901,7 @@ static unsigned int maxConnections = 8;
+ (void) initialize
{
GSTickerTimeNow();
if (null == nil)
{
null = [NSNull new];
}
[SQLRecord class]; // Force initialisation
if (clientsMap == 0)
{
clientsMap = NSCreateMapTable(NSObjectMapKeyCallBacks,
@ -770,6 +1025,30 @@ static unsigned int maxConnections = 8;
}
}
- (NSString*) buildQuery: (NSString*)stmt, ...
{
va_list ap;
NSString *sql = nil;
/*
* First check validity and concatenate parts of the query.
*/
va_start (ap, stmt);
sql = [[self _prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
return sql;
}
- (NSString*) buildQuery: (NSString*)stmt with: (NSDictionary*)values
{
NSString *sql = nil;
sql = [[self _substitute: stmt with: values] objectAtIndex: 0];
return sql;
}
- (NSString*) clientName
{
return _client;
@ -1447,9 +1726,19 @@ static unsigned int maxConnections = 8;
}
- (NSMutableArray*) simpleQuery: (NSString*)stmt
{
return [self simpleQuery: stmt recordClass: rClass];
}
- (NSMutableArray*) simpleQuery: (NSString*)stmt recordClass: (Class)cls
{
NSMutableArray *result = nil;
if (cls == 0)
{
[NSException raise: NSInvalidArgumentException
format: @"nil class passed to simpleQuery:recordClass:"];
}
[lock lock];
NS_DURING
{
@ -1459,7 +1748,7 @@ static unsigned int maxConnections = 8;
{
start = GSTickerTimeNow();
}
result = [self backendQuery: stmt];
result = [self backendQuery: stmt recordClass: cls];
_lastOperation = GSTickerTimeNow();
if (_duration >= 0)
{
@ -1514,6 +1803,11 @@ static unsigned int maxConnections = 8;
}
- (NSMutableArray*) backendQuery: (NSString*)stmt
{
return [self backendQuery: stmt recordClass: rClass];
}
- (NSMutableArray*) backendQuery: (NSString*)stmt recordClass: (Class)cls
{
[NSException raise: NSInternalInconsistencyException
format: @"Called -%@ without backend bundle loaded",
@ -2126,10 +2420,23 @@ static unsigned int maxConnections = 8;
return [self cache: seconds simpleQuery: stmt];
}
- (NSMutableArray*) cache: (int)seconds simpleQuery: (NSString*)stmt
- (NSMutableArray*) cache: (int)seconds
simpleQuery: (NSString*)stmt
{
return [self cache: seconds simpleQuery: stmt recordClass: rClass];
}
- (NSMutableArray*) cache: (int)seconds
simpleQuery: (NSString*)stmt
recordClass: (Class)cls
{
NSMutableArray *result = nil;
if (cls == 0)
{
[NSException raise: NSInvalidArgumentException
format: @"nil class passed to cache:simpleQuery:recordClass:"];
}
[lock lock];
NS_DURING
{
@ -2148,7 +2455,7 @@ static unsigned int maxConnections = 8;
if (result == nil)
{
result = toCache = [self backendQuery: stmt];
result = toCache = [self backendQuery: stmt recordClass: cls];
_lastOperation = GSTickerTimeNow();
if (_duration >= 0)
{

View file

@ -194,7 +194,7 @@
DESTROY(arp);
}
- (NSMutableArray*) backendQuery: (NSString*)stmt
- (NSMutableArray*) backendQuery: (NSString*)stmt recordClass: (Class)rClass
{
CREATE_AUTORELEASE_POOL(arp);
NSMutableArray *records = [[NSMutableArray alloc] init];
@ -281,9 +281,9 @@
}
}
record = [SQLRecord newWithValues: values
keys: keys
count: columns];
record = [rClass newWithValues: values
keys: keys
count: columns];
[records addObject: record];
RELEASE(record);
}