+2005-02-20 Helge Hess <helge.hess@opengroupware.org>
+
+ * most SQL operations based on models are implemented now (v4.5.9)
+
2005-02-20 Helge Hess <helge.hess@opengroupware.org>
* made gdltest work again
@implementation NSData(SQLiteValues)
-static NSData *EmptyData = nil;
-
-+ (id)valueFromCString:(const char *)_cstr length:(int)_length
- sqlite3Type:(NSString *)_type
- attribute:(EOAttribute *)_attribute
- adaptorChannel:(SQLiteChannel *)_channel
-{
- if (_length == 0) {
- if (EmptyData == nil) EmptyData = [[NSData alloc] init];
- return EmptyData;
- }
- return [[[self alloc] initWithBytes:_cstr length:_length] autorelease];
+- (id)initWithSQLiteInt:(int)_value {
+ return [self initWithBytes:&_value length:sizeof(int)];
}
-
-+ (id)valueFromBytes:(const void *)_bytes length:(int)_length
- sqlite3Type:(NSString *)_type
- attribute:(EOAttribute *)_attribute
- adaptorChannel:(SQLiteChannel *)_channel
-{
- if (_length == 0) {
- if (EmptyData == nil) EmptyData = [[NSData alloc] init];
- return EmptyData;
- }
- return [[[self alloc] initWithBytes:_bytes length:_length] autorelease];
+- (id)initWithSQLiteDouble:(double)_value {
+ return [self initWithBytes:&_value length:sizeof(double)];
+}
+- (id)initWithSQLiteText:(const unsigned char *)_value {
+ return [self initWithBytes:_value length:strlen(_value)];
+}
+- (id)initWithSQLiteData:(const void *)_value length:(int)_length {
+ return [self initWithBytes:_value length:_length];
}
- (NSString *)stringValueForSQLite3Type:(NSString *)_type
@implementation NSNumber(SQLiteValues)
-static Class NSNumberClass = Nil;
-static NSNumber *yesNum = nil;
-static NSNumber *noNum = nil;
-
-+ (id)valueFromCString:(const char *)_cstr length:(int)_length
- sqlite3Type:(NSString *)_type
- attribute:(EOAttribute *)_attribute
- adaptorChannel:(SQLiteChannel *)_channel
-{
- // TODO: can we avoid the lowercaseString?
- unsigned len;
- unichar c1;
-
- if ((len = [_type length]) == 0)
- return nil;
+- (id)initWithSQLiteInt:(int)_value {
+ return [self initWithInt:_value];
+}
+- (id)initWithSQLiteDouble:(double)_value {
+ return [self initWithDouble:_value];
+}
+- (id)initWithSQLiteText:(const unsigned char *)_value {
+ return index(_value, '.') != NULL
+ ? [self initWithDouble:atof(_value)]
+ : [self initWithInt:atoi(_value)];
+}
- if (NSNumberClass == Nil) NSNumberClass = [NSNumber class];
-
- c1 = [_type characterAtIndex:0];
- switch (c1) {
- case 'f': case 'F': {
- if (len < 5)
- break;
- if ([[_type lowercaseString] hasPrefix:@"float"])
- return [NSNumberClass numberWithDouble:atof(_cstr)];
- break;
- }
- case 's': case 'S': {
- if (len < 8)
- break;
- if ([[_type lowercaseString] hasPrefix:@"smallint"])
- return [NSNumberClass numberWithShort:atoi(_cstr)];
- break;
- }
- case 'i': case 'I': {
- if (len < 3)
- break;
- if ([[_type lowercaseString] hasPrefix:@"int"])
- return [NSNumberClass numberWithInt:atoi(_cstr)];
- }
- case 'b': case 'B': {
- if (len < 4)
- break;
- if (![[_type lowercaseString] hasPrefix:@"bool"])
- break;
-
- if (yesNum == nil) yesNum = [[NSNumberClass numberWithBool:YES] retain];
- if (noNum == nil) noNum = [[NSNumberClass numberWithBool:NO] retain];
-
- if (_length == 0)
- return noNum;
-
- switch (*_cstr) {
- case 't': case 'T':
- case 'y': case 'Y':
- case '1':
- return yesNum;
- default:
- return noNum;
- }
- }
+- (id)initWithSQLiteData:(const void *)_value length:(int)_length {
+ switch (_length) {
+ case 1: return [self initWithUnsignedChar:*(char *)_value];
+ case 2: return [self initWithShort:*(short *)_value];
+ case 4: return [self initWithInt:*(int *)_value];
+ case 8: return [self initWithDouble:*(double *)_value];
}
+
+ [self release];
return nil;
}
-+ (id)valueFromBytes:(const void *)_bytes length:(int)_length
- sqlite3Type:(NSString *)_type
- attribute:(EOAttribute *)_attribute
- adaptorChannel:(SQLiteChannel *)_channel
-{
- return [self notImplemented:_cmd];
-}
-
- (NSString *)stringValueForSQLite3Type:(NSString *)_type
attribute:(EOAttribute *)_attribute
{
/*
NSString+SQLite.h
- Copyright (C) 1999 MDlink online service center GmbH and Helge Hess
+ Copyright (C) 1999-2005 MDlink online service center GmbH and Helge Hess
Author: Helge Hess (helge@mdlink.de)
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-// $Id: NSString+SQLite.h,v 1.1 2004/06/14 14:27:44 helge Exp $
#ifndef ___SQLite_NSString_H___
#define ___SQLite_NSString_H___
@interface NSString(SQLiteMiscStrings)
-- (NSString *)_pgModelMakeInstanceVarName;
-- (NSString *)_pgModelMakeClassName;
-- (NSString *)_pgStringWithCapitalizedFirstChar;
-- (NSString *)_pgStripEndSpaces;
+- (NSString *)_sqlite3ModelMakeInstanceVarName;
+- (NSString *)_sqlite3ModelMakeClassName;
+- (NSString *)_sqlite3StringWithCapitalizedFirstChar;
+- (NSString *)_sqlite3StripEndSpaces;
@end
-#endif
+#endif /* ___SQLite_NSString_H___ */
@implementation NSString(SQLiteMiscStrings)
-- (NSString *)_pgModelMakeInstanceVarName {
+- (NSString *)_sqlite3ModelMakeInstanceVarName {
if ([self length] == 0)
return @"";
else {
}
}
-- (NSString *)_pgModelMakeClassName {
+- (NSString *)_sqlite3ModelMakeClassName {
if ([self length] == 0)
return @"";
else {
}
}
-- (NSString *)_pgStringWithCapitalizedFirstChar {
+- (NSString *)_sqlite3StringWithCapitalizedFirstChar {
NSCharacterSet *upperSet = [NSCharacterSet uppercaseLetterCharacterSet];
if ([self length] == 0)
}
}
-- (NSString *)_pgStripEndSpaces {
+- (NSString *)_sqlite3StripEndSpaces {
if ([self length] > 0) {
NSCharacterSet *spaceSet = [NSCharacterSet whitespaceCharacterSet];
NSMutableString *str = [NSMutableString stringWithCapacity:[self length]];
@implementation NSString(SQLiteValues)
-static Class NSStringClass = Nil;
-static Class EOExprClass = Nil;
+static Class EOExprClass = Nil;
-+ (id)valueFromCString:(const char *)_cstr length:(int)_length
- sqlite3Type:(NSString *)_type
- attribute:(EOAttribute *)_attribute
- adaptorChannel:(SQLiteChannel *)_channel
-{
- if (_cstr == NULL) return nil;
- if (*_cstr == '\0') return @"";
- if (NSStringClass == Nil) NSStringClass = [NSString class];
+- (id)initWithSQLiteInt:(int)_value {
+ char buf[256];
+ sprintf(buf, "%i", _value);
+ return [self initWithCString:buf];
+}
+- (id)initWithSQLiteDouble:(double)_value {
+ char buf[256];
+ sprintf(buf, "%g", _value);
+ return [self initWithCString:buf];
+}
- // TODO: cache IMP of selector
- return [NSStringClass stringWithCString:_cstr];
+- (id)initWithSQLiteText:(const unsigned char *)_value {
+ return [self initWithUTF8String:_value];
}
-+ (id)valueFromBytes:(const void *)_bytes length:(int)_length
- sqlite3Type:(NSString *)_type
- attribute:(EOAttribute *)_attribute
- adaptorChannel:(SQLiteChannel *)_channel
-{
- return [self notImplemented:_cmd];
+- (id)initWithSQLiteData:(const void *)_value length:(int)_length {
+ NSData *d;
+
+ d = [[NSData alloc] initWithBytes:_value length:_length];
+ self = [self initWithData:d encoding:NSUTF8StringEncoding];
+ [d release];
+ return self;
}
- (NSString *)stringValueForSQLite3Type:(NSString *)_type
TODO
====
-- rename methods with 'pg' in the name
- check EOAttribute+SQLite:
-loadValueClassAndTypeUsingSQLiteType:...
- SQLiteChannel.m:
-primaryFetchAttributes => check field name processing
+- rewrite for exception less operation
+- implement more methods in SQLiteChannel+Model (hard with SQLite though)
Basics
======
/*
SQLiteChannel+Model.m
- Copyright (C) 2003 SKYRIX Software AG
+ Copyright (C) 2003-2005 SKYRIX Software AG
Author: Helge Hess (helge.hess@skyrix.com)
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-// $Id: SQLiteChannel+Model.m,v 1.1 2004/06/14 14:27:44 helge Exp $
#include "SQLiteChannel.h"
#include "NSString+SQLite.h"
#include "EOAttribute+SQLite.h"
-#import "common.h"
+#include "common.h"
@interface EORelationship(FixMe)
- (void)addJoin:(id)_join;
NSArray *resultDescription;
NSDictionary *row;
- if (![_tableName length])
+ if ([_tableName length] == 0)
return nil;
- if (!(attributes = [self->_attributesForTableName
- objectForKey:_tableName])) {
- sqlExpr =
- @"SELECT a.attnum, a.attname, t.typname, a.attlen, a.attnotnull "
- @"FROM pg_class c, pg_attribute a, pg_type t "
- @"WHERE c.relname='%@' AND a.attnum>0 AND a.attrelid=c.oid AND a.atttypid=t.oid "
- @"ORDER BY attnum;";
+ attributes = [self->_attributesForTableName objectForKey:_tableName];
+ if (attributes == nil) {
+#if 1
+ // TODO: we would need to parse the SQL field of 'sqlite_master'?
+ NSLog(@"ERROR(%s): operation not supported on SQLite!",
+ __PRETTY_FUNCTION__);
+ return nil;
+#else
sqlExpr = [NSString stringWithFormat:sqlExpr, _tableName];
+#endif
if (![self evaluateExpression:sqlExpr]) {
fprintf(stderr,
NSString *attrName = nil;
columnName = [[row objectForKey:@"attname"] stringValue];
- attrName = [columnName _pgModelMakeInstanceVarName];
+ attrName = [columnName _sqlite3ModelMakeInstanceVarName];
externalType = [[row objectForKey:@"typname"] stringValue];
attribute = [[EOAttribute alloc] init];
[attribute setExternalType:externalType];
[attribute loadValueClassForExternalSQLiteType:externalType];
[attributes addObject:attribute];
- RELEASE(attribute);
+ [attribute release];
}
[self->_attributesForTableName setObject:attributes forKey:_tableName];
//NSLog(@"got attrs: %@", attributes);
}
- (NSArray *)_primaryKeysNamesForTableName:(NSString *)_tableName {
- NSArray *pkNameForTableName = nil;
+ //NSArray *pkNameForTableName = nil;
if ([_tableName length] == 0)
return nil;
- NSLog(@"%s: not supported on SQLite!", __PRETTY_FUNCTION__);
+ NSLog(@"ERROR(%s): operation not supported on SQLite!", __PRETTY_FUNCTION__);
return nil;
}
for (cnt = 0; cnt < tc; cnt++) {
NSMutableDictionary *relNamesUsed =
- AUTORELEASE([NSMutableDictionary new]);
+ [NSMutableDictionary dictionaryWithCapacity:16];
NSMutableArray *classProperties =
- AUTORELEASE([NSMutableArray new]);
+ [NSMutableArray arrayWithCapacity:16];
NSMutableArray *primaryKeyAttributes =
- AUTORELEASE([NSMutableArray new]);
+ [NSMutableArray arrayWithCapacity:2];
NSString *tableName = [_tableNames objectAtIndex:cnt];
NSArray *attributes = [self _attributesForTableName:tableName];
NSArray *pkeys = [self _primaryKeysNamesForTableName:tableName];
NSArray *fkeys = [self _foreignKeysForTableName:tableName];
- EOEntity *entity = AUTORELEASE([EOEntity new]);
+ EOEntity *entity = [[EOEntity new] autorelease];
int cnt2;
int ac = [attributes count];
int fkc = [fkeys count];
- [entity setName:[tableName _pgModelMakeClassName]];
+ [entity setName:[tableName _sqlite3ModelMakeClassName]];
[entity setClassName:
- [@"EO" stringByAppendingString:[tableName _pgModelMakeClassName]]];
+ [@"EO" stringByAppendingString:[tableName _sqlite3ModelMakeClassName]]];
[entity setExternalName:tableName];
[classProperties addObjectsFromArray:[entity classProperties]];
[primaryKeyAttributes addObjectsFromArray:[entity primaryKeyAttributes]];
NSString *da = [fkey objectForKey:@"targetAttr"];
NSString *dt = [fkey objectForKey:@"targetTable"];
EORelationship *rel =
- AUTORELEASE([[EORelationship alloc] init]);
+ [[[EORelationship alloc] init] autorelease];
EOJoin *join =
- AUTORELEASE([[EOJoin alloc] init]);
+ [[[EOJoin alloc] init] autorelease];
NSString *relName = nil;
- if ([pkeys containsObject:sa])
- relName = [@"to" stringByAppendingString:[dt _pgModelMakeClassName]];
+ if ([pkeys containsObject:sa]) {
+ relName = [@"to" stringByAppendingString:
+ [dt _sqlite3ModelMakeClassName]];
+ }
else {
relName = [@"to" stringByAppendingString:
- [[sa _pgModelMakeInstanceVarName]
- _pgStringWithCapitalizedFirstChar]];
+ [[sa _sqlite3ModelMakeInstanceVarName]
+ _sqlite3StringWithCapitalizedFirstChar]];
if ([relName hasSuffix:@"Id"]) {
int cLength = [relName cStringLength];
if ([relNamesUsed objectForKey:relName]) {
int useCount = [[relNamesUsed objectForKey:relName] intValue];
- [relNamesUsed setObject:[NSNumber numberWithInt:(useCount++)] forKey:relName];
+ [relNamesUsed setObject:[NSNumber numberWithInt:(useCount++)]
+ forKey:relName];
relName = [NSString stringWithFormat:@"%s%d",
[relName cString], useCount];
}
[relNamesUsed setObject:[NSNumber numberWithInt:0] forKey:relName];
[rel setName:relName];
- //[rel setDestinationEntity:(EOEntity *)[dt _pgModelMakeClassName]];
+ //[rel setDestinationEntity:(EOEntity *)[dt _sqlite3ModelMakeClassName]];
[rel setToMany:NO];
// TODO: EOJoin is removed, fix this ...
[(id)join setSourceAttribute:
- (EOAttribute *)[sa _pgModelMakeInstanceVarName]];
+ (EOAttribute *)[sa _sqlite3ModelMakeInstanceVarName]];
[(id)join setDestinationAttribute:
- (EOAttribute *)[da _pgModelMakeInstanceVarName]];
+ (EOAttribute *)[da _sqlite3ModelMakeInstanceVarName]];
[rel addJoin:join];
[entity addRelationship:rel];
[(EOEntity *)[reverse destinationEntity] name]];
else {
relName = [@"to" stringByAppendingString:
- [[[sa name] _pgModelMakeInstanceVarName]
- _pgStringWithCapitalizedFirstChar]];
+ [[[sa name] _sqlite3ModelMakeInstanceVarName]
+ _sqlite3StringWithCapitalizedFirstChar]];
if ([relName hasSuffix:@"Id"]) {
int cLength = [relName cStringLength];
NSDictionary *row = nil;
NSString *selectExpression = nil;
- selectExpression =
- @"SELECT relname "
- @"FROM pg_class "
- @"WHERE (relkind='r') AND relname !~ '^pg_' AND relname !~ '^xinv[0-9]+' "
- @"ORDER BY relname";
-
+ selectExpression = @"SELECT name FROM sqlite_master WHERE type='table'";
if (![self evaluateExpression:selectExpression]) {
- fprintf(stderr, "Couldn`t evaluate table-describe expression '%s'\n",
+ fprintf(stderr, "Could not evaluate table-describe expression '%s'\n",
[selectExpression cString]);
return nil;
}
resultDescription = [self describeResults];
- attributeName = [(EOAttribute *)[resultDescription objectAtIndex:0] name];
- tableNames = [NSMutableArray arrayWithCapacity:16];
-
- while ((row = [self fetchAttributes:resultDescription withZone:NULL]))
+ attributeName = [(EOAttribute *)[resultDescription objectAtIndex:0] name];
+ tableNames = [NSMutableArray arrayWithCapacity:16];
+
+ while ((row = [self fetchAttributes:resultDescription withZone:NULL])!=nil)
[tableNames addObject:[row objectForKey:attributeName]];
return tableNames;
void *_connection;
// valid during -evaluateExpression:
+ void *statement;
+ BOOL hasPendingRow;
+
void *results;
+#if 0
int tupleCount;
int fieldCount;
BOOL containsBinaryData;
NSString *cmdTuples;
NSString *oidStatus;
int currentTuple;
+#endif
// turns on/off channel debugging
BOOL isDebuggingEnabled;
if ((self = [super initWithAdaptorContext:_adaptorContext])) {
[self setDebugEnabled:[[NSUserDefaults standardUserDefaults]
boolForKey:@"SQLiteDebugEnabled"]];
- self->_attributesForTableName = [[NSMutableDictionary alloc]
- initWithCapacity:16];
+
+ self->_attributesForTableName =
+ [[NSMutableDictionary alloc] initWithCapacity:16];
self->_primaryKeysNamesForTableName =
[[NSMutableDictionary alloc] initWithCapacity:16];
}
}
- (int)maxOpenConnectionCount {
- static int MaxOpenConnectionCount = -1;
+ static int MaxOpenConnectionCount = -1;
- if (MaxOpenConnectionCount == -1) {
- MaxOpenConnectionCount =
- [[NSUserDefaults standardUserDefaults]
- integerForKey:@"SQLiteMaxOpenConnectionCount"];
- if (MaxOpenConnectionCount == 0) {
- MaxOpenConnectionCount = 15;
- }
- }
+ if (MaxOpenConnectionCount != -1)
return MaxOpenConnectionCount;
+
+ MaxOpenConnectionCount =
+ [[NSUserDefaults standardUserDefaults]
+ integerForKey:@"SQLiteMaxOpenConnectionCount"];
+ if (MaxOpenConnectionCount == 0)
+ MaxOpenConnectionCount = 15;
+ return MaxOpenConnectionCount;
}
- (BOOL)openChannel {
SQLiteAdaptor *adaptor;
int rc;
-
+
if (self->_connection) {
NSLog(@"%s: Connection already open !!!", __PRETTY_FUNCTION__);
return NO;
if (![super openChannel])
return NO;
-#if 0
- NSLog(@"+++++++++ %s: openConnectionCount %d", __PRETTY_FUNCTION__,
- openConnectionCount);
-#endif
- {
- if (openConnectionCount > [self maxOpenConnectionCount]) {
- [SQLiteCouldNotOpenChannelException
+ if (openConnectionCount > [self maxOpenConnectionCount]) {
+ [SQLiteCouldNotOpenChannelException
raise:@"NoMoreConnections"
format:@"cannot open a additional connection !"];
- return NO;
- }
+ return NO;
}
rc = sqlite3_open([[adaptor databaseName] UTF8String],
}
- (void)primaryCloseChannel {
- self->tupleCount = 0;
- self->fieldCount = 0;
- self->containsBinaryData = NO;
-
- if (self->results) {
- free(self->results);
- self->results = NO;
+ if (self->statement != NULL) {
+ sqlite3_finalize(self->statement);
+ self->statement = NULL;
}
-
- RELEASE(self->cmdStatus); self->cmdStatus = nil;
- RELEASE(self->cmdTuples); self->cmdTuples = nil;
- RELEASE(self->oidStatus); self->oidStatus = nil;
-
+
if (self->_connection != NULL) {
sqlite3_close(self->_connection);
#if 0
/* fetching rows */
-- (void)cancelFetch {
- if (![self isOpen]) {
- [SQLiteException raise:@"ChannelNotOpenException"
- format:@"No fetch in progress, connection is not open"
- @" (channel=%@)", self];
- }
-
-#if 0
- NSLog(@"canceling fetch (%i tuples remaining).",
- (self->tupleCount - self->currentTuple));
-#endif
+- (NSException *)_makeSQLiteStep {
+ NSString *r;
+ int rc;
- self->tupleCount = 0;
- self->currentTuple = 0;
- self->fieldCount = 0;
- self->containsBinaryData = NO;
-
- if (self->results) {
- free(self->results);
- self->results = NO;
+ rc = sqlite3_step(self->statement);
+
+ if (rc == SQLITE_ROW) {
+ self->hasPendingRow = YES;
+ return nil /* no error */;
+ }
+ if (rc == SQLITE_DONE) {
+ self->hasPendingRow = NO;
+ return nil /* no error */;
}
- RELEASE(self->cmdStatus); self->cmdStatus = nil;
- RELEASE(self->cmdTuples); self->cmdTuples = nil;
- RELEASE(self->oidStatus); self->oidStatus = nil;
+ if (rc == SQLITE_ERROR)
+ r = [NSString stringWithUTF8String:sqlite3_errmsg(self->_connection)];
+ else if (rc == SQLITE_MISUSE)
+ r = @"Somehow the SQLite method was called in an incorrect way.";
+ else if (rc == SQLITE_BUSY)
+ r = @"The SQLite is busy.";
+ else
+ r = [NSString stringWithFormat:@"Unexpected SQLite error: %i", rc];
+
+ return [SQLiteException exceptionWithName:@"FetchFailed"
+ reason:r userInfo:nil];
+}
+- (void)cancelFetch {
+ if (self->statement != NULL) {
+ sqlite3_finalize(self->statement);
+ self->statement = NULL;
+ }
[super cancelFetch];
}
- (NSArray *)describeResults {
- int cnt;
+ // TODO: make exception-less method
+ int cnt, fieldCount;
NSMutableArray *result = nil;
NSMutableDictionary *usedNames = nil;
NSNumber *yesObj;
[SQLiteException raise:@"NoFetchInProgress"
format:@"No fetch in progress (channel=%@)", self];
}
+
+ /* we need to fetch a row to get the info */
+
+ if (!self->hasPendingRow) {
+ NSException *error;
+
+ if ((error = [self _makeSQLiteStep]) != nil) {
+ [self cancelFetch];
+ [error raise]; // raise error, TODO: make exception-less method
+ return nil;
+ }
+ }
+ if (!self->hasPendingRow) /* no rows available */
+ return nil;
+
+ fieldCount = sqlite3_column_count(self->statement);
+
+#warning TODO: describe row
+ // allowsNull, columnName, externType, name, valueClassName, valueType
+ NSLog(@"%s: TODO describe current row ...", __PRETTY_FUNCTION__);
+
+ /* old code below */
- result = [[NSMutableArray alloc] initWithCapacity:self->fieldCount];
- usedNames = [[NSMutableDictionary alloc] initWithCapacity:self->fieldCount];
+ result = [[NSMutableArray alloc] initWithCapacity:fieldCount];
+ usedNames = [[NSMutableDictionary alloc] initWithCapacity:fieldCount];
- for (cnt = 0; cnt < self->fieldCount; cnt++) {
+ for (cnt = 0; cnt < fieldCount; cnt++) {
EOAttribute *attribute = nil;
NSString *columnName = nil;
NSString *attrName = nil;
-
-#if 1
-# warning TODO, columnName
-#else
- columnName = [NSString stringWithCString:self->fieldInfo[cnt].name];
-#endif
- attrName = [columnName _pgModelMakeInstanceVarName];
+
+ columnName = [NSString stringWithCString:
+ sqlite3_column_name(self->statement, cnt)];
+ attrName = [columnName _sqlite3ModelMakeInstanceVarName];
if ([[usedNames objectForKey:attrName] boolValue]) {
int cnt2 = 0;
NSString *newAttrName = nil;
for (cnt2 = 2; cnt2 < 100; cnt2++) {
+ NSString *s;
sprintf(buf, "%i", cnt2);
-
+
// TODO: unicode
- newAttrName = [attrName stringByAppendingString:
- [NSString stringWithCString:buf]];
+ s = [[NSString alloc] initWithCString:buf];
+ newAttrName = [attrName stringByAppendingString:s];
+ [s release];
if (![[usedNames objectForKey:newAttrName] boolValue]) {
attrName = newAttrName;
return [result autorelease];
}
+- (BOOL)isColumnNullInCurrentRow:(int)_column {
+ /*
+ Note: NULL is SQLite is represented as empty strings ..., don't know
+ what to do about that?
+ At least Sybase 10 doesn't support empty strings strings as well
+ and converts them to a single space. So maybe it is reasonable to
+ map empty strings to NSNull?
+
+ Or is this column-type SQLITE_NULL? If so, thats rather weird,
+ since the type query does not take a row.
+ */
+ return NO;
+}
+
- (NSMutableDictionary *)primaryFetchAttributes:(NSArray *)_attributes
withZone:(NSZone *)_zone
{
+ /*
+ Note: we expect that the attributes match the generated SQL. This is
+ because auto-generated SQL can contain SQL table prefixes (like
+ alias.column-name which cannot be detected using the attributes
+ schema)
+ */
+ // TODO: add a primaryFetchAttributesX method?
NSMutableDictionary *row = nil;
+ NSException *error;
unsigned attrCount = [_attributes count];
- int indices[attrCount];
unsigned cnt;
- if (self->currentTuple == self->tupleCount) {
- if (self->results) [self cancelFetch];
+ if (self->statement == NULL) {
+ NSLog(@"ERROR: no fetch in progress?");
+ [self cancelFetch];
return nil;
}
- {
-#if 1
-# warning TODO: field name processing
- NSMutableArray *fieldNames;
- //unsigned nFields, i;
-
- // TODO: we could probably cache the field-name array for much more speed !
- fieldNames = [[NSMutableArray alloc] initWithCapacity:32];
-#else
- nFields = PQnfields(self->results);
- for (i = 0; i < nFields; i++) {
- NSString *s;
-
- s = [[NSString alloc] initWithCString:PQfname(self->results, i)];
- [fieldNames addObject:s];
- [s release];
- }
-#endif
-
- for (cnt = 0; cnt < attrCount; cnt++) {
- EOAttribute *attribute = [_attributes objectAtIndex:cnt];
-
- indices[cnt] = [fieldNames indexOfObject:[attribute columnName]];
-
- if (indices[cnt] == NSNotFound) {
- [SQLiteException raiseWithFormat:
- @"attribute %@ not covered by query", attribute];
- }
- [fieldNames replaceObjectAtIndex:indices[cnt] withObject:[EONull null]];
+
+ if (!self->hasPendingRow) {
+ if ((error = [self _makeSQLiteStep]) != nil) {
+ [self cancelFetch];
+ [error raise]; // raise error, TODO: make exception-less method
+ return nil;
}
- [fieldNames release]; fieldNames = nil;
+ }
+ if (!self->hasPendingRow) { /* step was fine, but we are at the end */
+ [self cancelFetch];
+ return nil;
}
-
+ self->hasPendingRow = NO; /* consume the row */
+
+ /* build row */
+
row = [NSMutableDictionary dictionaryWithCapacity:attrCount];
for (cnt = 0; cnt < attrCount; cnt++) {
EOAttribute *attribute;
NSString *attrName;
id value = nil;
-
+
attribute = [_attributes objectAtIndex:cnt];
attrName = [attribute name];
-#if 1
-# warning TODO: value creation
-#else
- if (PQgetisnull(self->results, self->currentTuple, indices[cnt])) {
- value = null;
+ if ([self isColumnNullInCurrentRow:cnt]) {
+ value = [null retain];
}
else {
- Class valueClass = Nil;
- const char *pvalue;
- int vallen;
+ Class valueClass;
valueClass = NSClassFromString([attribute valueClassName]);
if (valueClass == Nil) {
continue;
}
- pvalue = PQgetvalue(self->results, self->currentTuple, indices[cnt]);
- vallen = PQgetlength(self->results, self->currentTuple, indices[cnt]);
-
- if (self->containsBinaryData) {
- // pvalue is stored in internal representation
-
- value = [valueClass valueFromBytes:pvalue length:vallen
- sqlite3Type:[attribute externalType]
- attribute:attribute
- adaptorChannel:self];
- }
- else {
- // pvalue is ASCII string
-
- value = [valueClass valueFromCString:pvalue length:vallen
- sqlite3Type:[attribute externalType]
- attribute:attribute
- adaptorChannel:self];
+ switch (sqlite3_column_type(self->statement, cnt)) {
+ case SQLITE_INTEGER:
+ value = [[valueClass alloc]
+ initWithSQLiteInt:sqlite3_column_int(self->statement, cnt)];
+ break;
+ case SQLITE_FLOAT:
+ value = [[valueClass alloc]
+ initWithSQLiteDouble:
+ sqlite3_column_double(self->statement, cnt)];
+ break;
+ case SQLITE_TEXT:
+ value = [[valueClass alloc]
+ initWithSQLiteText:
+ sqlite3_column_text(self->statement, cnt)];
+ break;
+ case SQLITE_BLOB:
+ value = [[valueClass alloc]
+ initWithSQLiteData:
+ sqlite3_column_blob(self->statement, cnt)
+ length:sqlite3_column_bytes(self->statement, cnt)];
+ break;
+ case SQLITE_NULL:
+ value = [null retain];
+ break;
+ default:
+ NSLog(@"ERROR(%s): unexpected SQLite type at column %i",
+ __PRETTY_FUNCTION__, cnt);
+ continue;
}
+
if (value == nil) {
NSLog(@"ERROR(%s): %@: got no value for column:\n"
@" attribute=%@\n valueClass=%@\n type=%@",
continue;
}
}
-#endif
-
- [row setObject:value forKey:attrName];
+
+ if (value != nil) {
+ [row setObject:value forKey:attrName];
+ [value release];
+ }
}
-
- self->currentTuple++;
-
+
return row;
}
/* sending SQL to server */
-static int sqlite_result_callback
-(void *userdata, int columnCount, char **columns, char **columnNames)
-{
- /* need to load into array ... */
- SQLiteChannel *self = userdata;
-
- NSLog(@"%@: SQLite callback, %i columns ...", self, columnCount);
- return 0;
-}
-
-- (BOOL)evaluateExpression:(NSString *)_expression {
- char *zErrMsg = NULL;
- BOOL result;
+- (NSException *)evaluateExpressionX:(NSString *)_expression {
+ NSMutableString *sql;
+ NSException *error;
+ char *zErrMsg = NULL;
+ BOOL result;
+ const char *s;
+ const char *tails = NULL;
int rc;
*(&result) = YES;
-
+
if (_expression == nil) {
[NSException raise:@"InvalidArgumentException"
format:@"parameter for evaluateExpression: "
@"must not be null (channel=%@)", self];
}
- *(&_expression) = [[_expression mutableCopy] autorelease];
+ sql = [[_expression mutableCopy] autorelease];
+ /* ask delegate */
+
if (delegateRespondsTo.willEvaluateExpression) {
EODelegateResponse response;
-
- response =
- [delegate adaptorChannel:self
- willEvaluateExpression:(NSMutableString *)_expression];
- if (response == EODelegateRejects)
- return NO;
+ response = [delegate adaptorChannel:self willEvaluateExpression:sql];
+
+ if (response == EODelegateRejects) {
+ return [NSException exceptionWithName:@"EODelegateRejects"
+ reason:@"delegate rejected insert"
+ userInfo:nil];
+ }
if (response == EODelegateOverrides)
- return YES;
+ return nil;
}
+
+ /* check some preconditions */
if (![self isOpen]) {
- [SQLiteException raise:@"ChannelNotOpenException"
- format:@"SQLite connection is not open (channel=%@)",
- self];
- return NO;
+ return [SQLiteException exceptionWithName:@"ChannelNotOpenException"
+ reason:@"SQLite connection is not open"
+ userInfo:nil];
}
- if (self->results != NULL) {
- [SQLiteException raise:@"CommandInProgressException"
- format:@"an evaluation is in progress (channel=%@)",self];
+ if (self->statement != NULL) {
+ return [SQLiteException exceptionWithName:@"CommandInProgressException"
+ reason:@"an evaluation is in progress"
+ userInfo:nil];
return NO;
}
-
+
+ if ([self isFetchInProgress]) {
+ NSLog(@"WARNING: a fetch is still in progress: %@", self);
+ [self cancelFetch];
+ }
+
if (isDebuggingEnabled)
- NSLog(@"%@ sql: %@", self, _expression);
+ NSLog(@"%@ SQL: %@", self, sql);
+ /* reset environment */
+
self->isFetchInProgress = NO;
- self->tupleCount = 0;
- self->fieldCount = 0;
- self->currentTuple = 0;
- self->containsBinaryData = NO;
-
- rc = sqlite3_exec(self->_connection,
- [_expression UTF8String],
- sqlite_result_callback,
- self /* userdata */,
- &zErrMsg);
+
+ s = [sql UTF8String];
+ rc = sqlite3_prepare(self->_connection, s, strlen(s),
+ (void *)&(self->statement), &tails);
+
if (rc != SQLITE_OK) {
- NSString *err;
-
- if (zErrMsg) {
- err = [NSString stringWithCString:zErrMsg];
- free(zErrMsg);
+ [self cancelFetch];
+ // TODO: improve error
+ return [SQLiteException exceptionWithName:@"ExecutionFailed"
+ reason:@"could not parse SQL statement"
+ userInfo:nil];
+ }
+
+ /* step to first row */
+
+ if ([sql hasPrefix:@"SELECT"] || [sql hasPrefix:@"select"]) {
+ self->isFetchInProgress = YES;
+ NSAssert(self->statement, @"missing statement");
+ }
+ else {
+ if ((error = [self _makeSQLiteStep]) != nil) {
+ [self cancelFetch];
+ return error;
+ }
+
+ self->isFetchInProgress = self->hasPendingRow;
+ if (!self->isFetchInProgress) {
+ sqlite3_finalize(self->statement);
+ self->statement = NULL;
}
- else
- err = nil;
-
- [SQLiteException raise:@"ExecutionFailed"
- format:
- @"the sqlite_exec(%@) call failed (channel=%@): %s",
- _expression, self, err];
- return NO;
}
- if (zErrMsg) {
- NSLog(@"WARNING(%@): pending error message: '%s'", self, zErrMsg);
+
+ /* check whether there are pending errors */
+
+ if (zErrMsg != NULL) {
+ NSLog(@"WARNING(%s): %@ pending error message: '%s'",
+ __PRETTY_FUNCTION__, self, zErrMsg);
free(zErrMsg);
}
-
+
/* only on empty results? */
if (delegateRespondsTo.didEvaluateExpression)
- [delegate adaptorChannel:self didEvaluateExpression:_expression];
+ [delegate adaptorChannel:self didEvaluateExpression:sql];
+ return nil /* everything is OK */;
+}
+- (BOOL)evaluateExpression:(NSString *)_sql {
+ NSException *e;
+ NSString *n;
+
+ if ((e = [self evaluateExpressionX:_sql]) == nil)
+ return YES;
+
+ /* for compatibility with non-X methods, translate some errors to a bool */
+ n = [e name];
+ if ([n isEqualToString:@"EOEvaluationError"])
+ return NO;
+ if ([n isEqualToString:@"EODelegateRejects"])
+ return NO;
+
+ [e raise];
return NO;
}
pkey = nil;
seq = nil;
- if ([seqName length] > 0)
- seq = [NSString stringWithFormat:@"SELECT NEXTVAL ('%@')", seqName];
- else
- seq = [adaptor newKeyExpression];
+ seq = ([seqName length] > 0)
+ ? [NSString stringWithFormat:@"SELECT NEXTVAL ('%@')", seqName]
+ : [adaptor newKeyExpression];
NS_DURING {
if ([self evaluateExpression:seq]) {
id key = nil;
-#if 0
- if (self->tupleCount > 0) {
- if (PQgetisnull(self->results, 0, 0))
- key = null;
- else {
- const char *pvalue;
- int vallen;
-
- if (self->containsBinaryData) {
- [self notImplemented:_cmd];
- }
-
- pvalue = PQgetvalue(self->results, 0, 0);
- vallen = PQgetlength(self->results, 0, 0);
-
- if (pvalue)
- key = [NSNumber numberWithInt:atoi(pvalue)];
- }
+
+ NSLog(@"ERROR: new key creation is not implemented in SQLite yet!");
+ if ([self isFetchInProgress]) {
+ NSLog(@"Primary key eval returned results ..");
}
-#endif
// TODO
NSLog(@"%s: PKEY GEN NOT IMPLEMENTED!", __PRETTY_FUNCTION__);
[self cancelFetch];
- if (key) {
+ if (key != nil) {
pkey = [NSDictionary dictionaryWithObject:key
forKey:[pkeys objectAtIndex:0]];
}
- (BOOL)primaryBeginTransaction {
BOOL result;
-
+
result = [[[channels lastObject]
nonretainedObjectValue]
evaluateExpression:@"BEGIN TRANSACTION"];
-
+
return result;
}
@protocol SQLiteValues
-+ (id)valueFromCString:(const char *)_cstr length:(int)_length
- sqlite3Type:(NSString *)_type
- attribute:(EOAttribute *)_attribute
- adaptorChannel:(SQLiteChannel *)_channel;
-
-+ (id)valueFromBytes:(const void *)_bytes length:(int)_length
- sqlite3Type:(NSString *)_type
- attribute:(EOAttribute *)_attribute
- adaptorChannel:(SQLiteChannel *)_channel;
-
- (NSString *)stringValueForSQLite3Type:(NSString *)_type
attribute:(EOAttribute *)_attribute;
@end
+@interface NSObject(SQLiteValues)
+
+- (id)initWithSQLiteInt:(int)_value;
+- (id)initWithSQLiteText:(const unsigned char *)_value;
+- (id)initWithSQLiteDouble:(double)_value;
+- (id)initWithSQLiteData:(const void *)_data length:(int)_length;
+
+@end
+
@interface NSString(SQLiteValues) < SQLiteValues >
@end
@end /* NSNull(SQLiteValues) */
+@implementation NSObject(SQLiteValues)
+
+- (id)initWithSQLiteInt:(int)_value {
+ if ([self respondsToSelector:@selector(initWithInt:)])
+ return [(NSNumber *)self initWithInt:_value];
+
+ if ([self respondsToSelector:@selector(initWithDouble:)])
+ return [(NSNumber *)self initWithDouble:_value];
+
+ if ([self respondsToSelector:@selector(initWithString:)]) {
+ NSString *s;
+ char buf[256];
+
+ sprintf(buf, "%i", _value);
+ s = [[NSString alloc] initWithCString:buf];
+ self = [(NSString *)self initWithString:s];
+ [s release];
+ return self;
+ }
+
+ [self release];
+ return nil;
+}
+
+- (id)initWithSQLiteDouble:(double)_value {
+ if ([self respondsToSelector:@selector(initWithDouble:)])
+ return [(NSNumber *)self initWithDouble:_value];
+
+ [self release];
+ return nil;
+}
+
+- (id)initWithSQLiteText:(const unsigned char *)_value {
+ if ([self respondsToSelector:@selector(initWithString:)]) {
+ NSString *s;
+
+ s = [[NSString alloc] initWithUTF8String:_value];
+ self = [(NSString *)self initWithString:s];
+ [s release];
+ return self;
+ }
+
+ [self release];
+ return nil;
+}
+
+- (id)initWithSQLiteData:(const void *)_data length:(int)_length {
+ if ([self respondsToSelector:@selector(initWithBytes:length:)])
+ return [(NSData *)self initWithBytes:_data length:_length];
+
+ if ([self respondsToSelector:@selector(initWithData:)]) {
+ NSData *d;
+
+ d = [[NSData alloc] initWithBytes:_data length:_length];
+ self = [(NSData *)self initWithData:d];
+ [d release];
+ return self;
+ }
+
+ [self release];
+ return nil;
+}
+
+@end /* NSObject(SQLiteValues) */
void __link_SQLiteValues() {
// used to force linking of object file
-# $Id: Version,v 1.1 2004/06/14 14:27:44 helge Exp $
+# Version file
-SUBMINOR_VERSION:=8
+SUBMINOR_VERSION:=9
#import <GDLAccess/GDLAccess.h>
#include <NGExtensions/NGExtensions.h>
+static void fetchExprInChannel(NSString *expr, EOAdaptorChannel *ch) {
+ NSArray *attrs;
+ NSDictionary *record;
+
+ if (![expr isNotNull]) return;
+
+ if (![ch evaluateExpression:expr]) {
+ NSLog(@"ERROR: failed to evaluate: %@", expr);
+ return;
+ }
+
+ attrs = [ch describeResults];
+ NSLog(@"results: %@", attrs);
+
+ while ((record = [ch fetchAttributes:attrs withZone:nil]) != nil)
+ NSLog(@"fetched %@", record);
+}
+
+static void fetchSomePersonRecord(EOEntity *e, EOAdaptorChannel *ch) {
+ EOSQLQualifier *q;
+ NSArray *attrs;
+
+ attrs = [e attributes];
+ q = [[EOSQLQualifier alloc]
+ initWithEntity:e
+ qualifierFormat:@"%A='helge'", @"login"];
+ [q autorelease];
+
+ if ([ch selectAttributes:attrs
+ describedByQualifier:q
+ fetchOrder:nil
+ lock:NO]) {
+ NSDictionary *record;
+
+ record = [ch fetchAttributes:attrs withZone:nil];
+ }
+ else
+ NSLog(@"Could not select ..");
+}
+
+static void fetchSomeTeamRecords(EOEntity *e, EOAdaptorChannel *ch) {
+ EOSQLQualifier *q;
+ NSArray *attrs;
+
+ q = [e qualifier];
+ attrs = [e attributes];
+
+ if ([ch selectAttributes:attrs describedByQualifier:q fetchOrder:nil
+ lock:NO]) {
+ NSDictionary *record;
+
+ while ((record = [ch fetchAttributes:attrs withZone:NULL]) != nil) {
+ NSLog(@"fetched %@ birthday %@",
+ [record valueForKey:@"description"],
+ [record valueForKey:@"companyId"]);
+ }
+ }
+ else
+ NSLog(@"Could not select team records ..");
+}
+
static void runtestInOpenChannel(EOAdaptorChannel *ch) {
+ NSAutoreleasePool *pool = [NSAutoreleasePool new];
+ EOEntity *e;
+ EOSQLQualifier *q;
+ NSArray *attrs;
EOAdaptorContext *ctx;
EOModel *m;
NSString *expr;
expr = [[NSUserDefaults standardUserDefaults] stringForKey:@"sql"];
NSLog(@"channel is open");
-
- if ([ctx beginTransaction]) {
- NSLog(@"began tx ..");
+
+ if (![ctx beginTransaction]) {
+ NSLog(@"ERROR: could not begin transaction ...");
+ return;
+ }
- /* do something */
- {
- NSAutoreleasePool *pool = [NSAutoreleasePool new];
- EOEntity *e;
- EOSQLQualifier *q;
- NSArray *attrs;
+ NSLog(@"began tx ..");
+ /* do something */
+ pool = [[NSAutoreleasePool alloc] init];
#if 1
- /* fetch some expr */
-
- if (expr) {
- if ([ch evaluateExpression:expr]) {
- NSDictionary *record;
-
- attrs = [ch describeResults];
- NSLog(@"results: %@", attrs);
-
- while ((record = [ch fetchAttributes:attrs withZone:nil]))
- NSLog(@"fetched %@", record);
- }
- }
+ fetchExprInChannel(expr, ch);
#endif
- /* fetch some doof records */
-
- e = [m entityNamed:@"MyEntity"];
- NSLog(@"entity: %@", e);
- if (e == nil)
- exit(1);
-
- q = [e qualifier];
- attrs = [e attributes];
-
- if ([ch selectAttributes:attrs
- describedByQualifier:q
- fetchOrder:nil
- lock:NO]) {
- NSDictionary *record;
-
- while ((record = [ch fetchAttributes:attrs withZone:nil])) {
- NSLog(@"fetched %@ birthday %@",
- [record valueForKey:@"pkey"],
- [record valueForKey:@"companyId"]);
- }
- }
- else
- NSLog(@"Could not select ..");
-
- /* fetch some team records */
-
- if ((e = [m entityNamed:@"Team"])) {
- q = [e qualifier];
- attrs = [e attributes];
-
- if ([ch selectAttributes:attrs
- describedByQualifier:q
- fetchOrder:nil
- lock:NO]) {
- NSDictionary *record;
-
- while ((record = [ch fetchAttributes:attrs withZone:nil])) {
- NSLog(@"fetched %@ birthday %@",
- [record valueForKey:@"description"],
- [record valueForKey:@"companyId"]);
- }
- }
- else
- NSLog(@"Could not select ..");
- }
+
+ /* fetch some MyEntity records */
+
+ e = [m entityNamed:@"MyEntity"];
+ NSLog(@"entity: %@", e);
+ if (e == nil)
+ exit(1);
- /* do some update */
-
- if ((e = [m entityNamed:@"Person"])) {
- attrs = [e attributes];
- q = [[EOSQLQualifier alloc]
- initWithEntity:e
- qualifierFormat:@"%A='helge'", @"login"];
- AUTORELEASE(q);
-
- if ([ch selectAttributes:attrs
- describedByQualifier:q
- fetchOrder:nil
- lock:NO]) {
- NSDictionary *record;
-
- record = [ch fetchAttributes:attrs withZone:nil];
- }
- else
- NSLog(@"Could not select ..");
- }
-
- RELEASE(pool);
- }
+ q = [e qualifier];
+ attrs = [e attributes];
+
+ // NSLog(@"ATTRS: %@", attrs);
+
+ if ([ch selectAttributes:attrs
+ describedByQualifier:q
+ fetchOrder:nil
+ lock:NO]) {
+ NSDictionary *record;
+
+ while ((record = [ch fetchAttributes:attrs withZone:nil]) != nil)
+ NSLog(@"fetched record: %@", record);
+ }
+ else
+ NSLog(@"Could not select ..");
+
+ /* some OGo fetches */
+
+ if ((e = [m entityNamed:@"Team"]) != nil)
+ fetchSomeTeamRecords(e, ch);
+
+ if ((e = [m entityNamed:@"Person"]) != nil)
+ fetchSomePersonRecord(e, ch);
+
+ /* tear down */
+
+ [pool release];
- NSLog(@"committing tx ..");
- if ([ctx commitTransaction])
- NSLog(@" could commit.");
- else
- NSLog(@" commit failed.");
- }
+ NSLog(@"committing tx ..");
+ if ([ctx commitTransaction])
+ NSLog(@" could commit.");
+ else
+ NSLog(@" commit failed.");
}
static void runtest(void) {