commit dbfce3528e370166a78f3edef6f5b62464afa3c4 Author: Jonathan Schleifer Date: Wed Oct 3 13:20:06 2012 +0000 Initial import. FossilOrigin-Name: de46b0e10c5d9f516acbbf2ea01d84d0d5ac126412949d459265279262a1b87e diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e344dde --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build +*.dll +*.dylib +*.so +*~ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..056a6d8 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +SRCS = PGConnection.m \ + PGResult.m \ + PGResultRow.m \ + exceptions/PGCommandFailedException.m \ + exceptions/PGConnectionFailedException.m \ + exceptions/PGException.m + +all: + @objfw-compile \ + --lib 0.0 \ + -o objpgsql \ + --builddir build \ + -Iexceptions \ + -I. \ + -lpq \ + ${SRCS} + +test: + @objfw-compile \ + -o test \ + --builddir build \ + -Iexceptions \ + -I. \ + -L. \ + -lobjpgsql \ + test.m + +clean: + rm -f libobjpgsql.* exceptions/*~ *~ + rm -fr build diff --git a/PGConnection.h b/PGConnection.h new file mode 100644 index 0000000..3026b5c --- /dev/null +++ b/PGConnection.h @@ -0,0 +1,25 @@ +#include + +#import + +#import "PGResult.h" + +@interface PGConnection: OFObject +{ + PGconn *conn; + OFDictionary *parameters; +} + +#ifdef OF_HAVE_PROPERTIES +@property (copy) OFDictionary *parameters; +#endif + +- (void)setParameters: (OFDictionary*)parameters; +- (OFDictionary*)parameters; +- (void)connect; +- (void)reset; +- (PGResult*)executeCommand: (OFString*)command; +- (PGResult*)executeCommand: (OFString*)command + parameters: (OFArray*)parameters; +- (PGconn*)PG_connection; +@end diff --git a/PGConnection.m b/PGConnection.m new file mode 100644 index 0000000..31bb386 --- /dev/null +++ b/PGConnection.m @@ -0,0 +1,128 @@ +#import "PGConnection.h" + +#import "PGConnectionFailedException.h" +#import "PGCommandFailedException.h" + +@implementation PGConnection +- (void)dealloc +{ + [parameters release]; + + if (conn != NULL) + PQfinish(conn); + + [super dealloc]; +} + +- (void)setParameters: (OFDictionary*)parameters_ +{ + OF_SETTER(parameters, parameters_, YES, YES) +} + +- (OFDictionary*)parameters +{ + OF_GETTER(parameters, YES) +} + +- (void)connect +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + OFEnumerator *keyEnumerator = [parameters keyEnumerator]; + OFEnumerator *objectEnumerator = [parameters objectEnumerator]; + OFMutableString *conninfo = nil; + OFString *key, *object; + + while ((key = [keyEnumerator nextObject]) != nil && + (object = [objectEnumerator nextObject]) != nil) { + if (conninfo != nil) + [conninfo appendFormat: @" %@=%@", key, object]; + else + conninfo = [OFMutableString stringWithFormat: + @"%@=%@", key, object]; + } + + if ((conn = PQconnectdb([conninfo UTF8String])) == NULL) + @throw [OFOutOfMemoryException + exceptionWithClass: [self class]]; + + if (PQstatus(conn) == CONNECTION_BAD) + @throw [PGConnectionFailedException + exceptionWithClass: [self class] + connection: self]; + + [pool release]; +} + +- (void)reset +{ + PQreset(conn); +} + +- (PGResult*)executeCommand: (OFString*)command +{ + PGresult *result = PQexec(conn, [command UTF8String]); + + if (PQresultStatus(result) == PGRES_FATAL_ERROR) { + PQclear(result); + @throw [PGCommandFailedException + exceptionWithClass: [self class] + connection: self + command: command]; + } + + if (PQresultStatus(result) == PGRES_TUPLES_OK) + return [PGResult PG_resultWithResult: result]; + + PQclear(result); + return nil; +} + +- (PGResult*)executeCommand: (OFString*)command + parameters: (OFArray*)parameters_ +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + PGresult *result; + const char **values; + + values = [self allocMemoryWithSize: sizeof(*values) + count: [parameters_ count]]; + @try { + OFEnumerator *enumerator = [parameters_ objectEnumerator]; + size_t i = 0; + id parameter; + + while ((parameter = [enumerator nextObject]) != nil) { + if ([parameter isKindOfClass: [OFNull class]]) + values[i++] = NULL; + else + values[i++] = [parameter UTF8String]; + } + + result = PQexecParams(conn, [command UTF8String], + [parameters_ count], NULL, values, NULL, NULL, 0); + } @finally { + [self freeMemory: values]; + } + + [pool release]; + + if (PQresultStatus(result) == PGRES_FATAL_ERROR) { + PQclear(result); + @throw [PGCommandFailedException + exceptionWithClass: [self class] + connection: self + command: command]; + } + + if (PQresultStatus(result) == PGRES_TUPLES_OK) + return [PGResult PG_resultWithResult: result]; + + PQclear(result); + return nil; +} + +- (PGconn*)PG_connection +{ + return conn; +} +@end diff --git a/PGResult.h b/PGResult.h new file mode 100644 index 0000000..8b75dea --- /dev/null +++ b/PGResult.h @@ -0,0 +1,13 @@ +#include + +#import + +@interface PGResult: OFArray +{ + PGresult *result; +} + ++ PG_resultWithResult: (PGresult*)result; +- PG_initWithResult: (PGresult*)result; +- (PGresult*)PG_result; +@end diff --git a/PGResult.m b/PGResult.m new file mode 100644 index 0000000..aa0fb24 --- /dev/null +++ b/PGResult.m @@ -0,0 +1,46 @@ +#import "PGResult.h" +#import "PGResultRow.h" + +@implementation PGResult ++ PG_resultWithResult: (PGresult*)result +{ + return [[[self alloc] PG_initWithResult: result] autorelease]; +} + +- PG_initWithResult: (PGresult*)result_ +{ + self = [super init]; + + result = result_; + + return self; +} + +- (void)dealloc +{ + if (result != NULL) + PQclear(result); + + [super dealloc]; +} + +- (size_t)count +{ + return PQntuples(result); +} + +- (id)objectAtIndex: (size_t)index +{ + if (index > PQntuples(result)) + @throw [OFOutOfRangeException + exceptionWithClass: [self class]]; + + return [PGResultRow rowWithResult: self + row: index]; +} + +- (PGresult*)PG_result +{ + return result; +} +@end diff --git a/PGResultRow.h b/PGResultRow.h new file mode 100644 index 0000000..b71b22b --- /dev/null +++ b/PGResultRow.h @@ -0,0 +1,18 @@ +#include + +#import + +#import "PGResult.h" + +@interface PGResultRow: OFDictionary +{ + PGResult *result; + PGresult *res; + size_t row; +} + ++ rowWithResult: (PGResult*)result + row: (size_t)row; +- initWithResult: (PGResult*)result + row: (size_t)row; +@end diff --git a/PGResultRow.m b/PGResultRow.m new file mode 100644 index 0000000..2c37264 --- /dev/null +++ b/PGResultRow.m @@ -0,0 +1,117 @@ +#import "PGResultRow.h" + +@interface PGResultRowEnumerator: OFEnumerator +{ + PGResult *result; + PGresult *res; + size_t row, pos, count; +} + +- initWithResult: (PGResult*)result + row: (size_t)row; +@end + +@interface PGResultRowKeyEnumerator: PGResultRowEnumerator +@end + +@interface PGResultRowObjectEnumerator: PGResultRowEnumerator +@end + +@implementation PGResultRow ++ rowWithResult: (PGResult*)result + row: (size_t)row +{ + return [[[self alloc] initWithResult: result + row: row] autorelease]; +} + +- initWithResult: (PGResult*)result_ + row: (size_t)row_ +{ + self = [super init]; + + result = [result_ retain]; + res = [result PG_result]; + row = row_; + + return self; +} + +- (void)dealloc +{ + [result release]; + + [super dealloc]; +} + +- (size_t)count +{ + return PQnfields(res); +} + +- (id)objectForKey: (id)key +{ + int col; + + if ([key isKindOfClass: [OFNumber class]]) + col = [key intValue]; + else + col = PQfnumber(res, [key UTF8String]); + + return [OFString stringWithUTF8String: PQgetvalue(res, row, col)]; +} + +- (OFEnumerator*)keyEnumerator +{ + return [[[PGResultRowKeyEnumerator alloc] + initWithResult: result + row: row] autorelease]; +} + +- (OFEnumerator*)objectEnumerator +{ + return [[[PGResultRowObjectEnumerator alloc] + initWithResult: result + row: row] autorelease]; +} +@end + +@implementation PGResultRowEnumerator +- initWithResult: (PGResult*)result_ + row: (size_t)row_ +{ + self = [super init]; + + result = [result_ retain]; + res = [result PG_result]; + row = row_; + count = PQnfields(res); + + return self; +} + +- (void)reset +{ + pos = 0; +} +@end + +@implementation PGResultRowKeyEnumerator +- (id)nextObject +{ + if (pos >= count) + return nil; + + return [OFString stringWithUTF8String: PQfname(res, pos++)]; +} +@end + +@implementation PGResultRowObjectEnumerator +- (id)nextObject +{ + if (pos >= count) + return nil; + + return [OFString stringWithUTF8String: PQgetvalue(res, row, pos++)]; +} +@end diff --git a/exceptions/PGCommandFailedException.h b/exceptions/PGCommandFailedException.h new file mode 100644 index 0000000..18a3316 --- /dev/null +++ b/exceptions/PGCommandFailedException.h @@ -0,0 +1,19 @@ +#import "PGException.h" + +@interface PGCommandFailedException: PGException +{ + OFString *command; +} + +#ifdef OF_HAVE_PROPERTIES +@property (readonly, copy, nonatomic) OFString *command; +#endif + ++ exceptionWithClass: (Class)class_ + connection: (PGConnection*)connection + command: (OFString*)command; +- initWithClass: (Class)class_ + connection: (PGConnection*)connection + command: (OFString*)command; +- (OFString*)command; +@end diff --git a/exceptions/PGCommandFailedException.m b/exceptions/PGCommandFailedException.m new file mode 100644 index 0000000..3990e41 --- /dev/null +++ b/exceptions/PGCommandFailedException.m @@ -0,0 +1,53 @@ +#import "PGCommandFailedException.h" + +@implementation PGCommandFailedException ++ exceptionWithClass: (Class)class + connection: (PGConnection*)connection + command: (OFString*)command +{ + return [[[self alloc] initWithClass: class + connection: connection + command: command] autorelease]; +} + +- initWithClass: (Class)class_ + connection: (PGConnection*)connection_ + command: (OFString*)command_ +{ + self = [super initWithClass: class_ + connection: connection_]; + + @try { + command = [command_ copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [command release]; + + [super dealloc]; +} + +- (OFString*)description +{ + if (description != nil) + return description; + + description = [[OFString alloc] initWithFormat: + @"A PostgreSQL command in class %@ failed: %s\nCommand: %@", + inClass, PQerrorMessage([connection PG_connection]), command]; + + return description; +} + +- (OFString*)command +{ + OF_GETTER(command, NO) +} +@end diff --git a/exceptions/PGConnectionFailedException.h b/exceptions/PGConnectionFailedException.h new file mode 100644 index 0000000..6766321 --- /dev/null +++ b/exceptions/PGConnectionFailedException.h @@ -0,0 +1,4 @@ +#import "PGException.h" + +@interface PGConnectionFailedException: PGException +@end diff --git a/exceptions/PGConnectionFailedException.m b/exceptions/PGConnectionFailedException.m new file mode 100644 index 0000000..dc3f4b5 --- /dev/null +++ b/exceptions/PGConnectionFailedException.m @@ -0,0 +1,21 @@ +#import "PGConnectionFailedException.h" + +@implementation PGConnectionFailedException +- (OFString*)description +{ + OFAutoreleasePool *pool; + + if (description != nil) + return description; + + pool = [[OFAutoreleasePool alloc] init]; + description = [[OFString alloc] initWithFormat: + @"Establishing a PostgreSQL connection in class %@ failed:\n%s\n" + "Parameters: %@", inClass, + PQerrorMessage([connection PG_connection]), + [connection parameters]]; + [pool release]; + + return description; +} +@end diff --git a/exceptions/PGException.h b/exceptions/PGException.h new file mode 100644 index 0000000..3e9b73b --- /dev/null +++ b/exceptions/PGException.h @@ -0,0 +1,19 @@ +#import + +#import "PGConnection.h" + +@interface PGException: OFException +{ + PGConnection *connection; +} + +#ifdef OF_HAVE_PROPERTIES +@property (readonly, retain, nonatomic) PGConnection *connection; +#endif + ++ exceptionWithClass: (Class)class_ + connection: (PGConnection*)connection; +- initWithClass: (Class)class_ + connection: (PGConnection*)connection; +- (PGConnection*)connection; +@end diff --git a/exceptions/PGException.m b/exceptions/PGException.m new file mode 100644 index 0000000..c4103b2 --- /dev/null +++ b/exceptions/PGException.m @@ -0,0 +1,44 @@ +#import "PGException.h" + +@implementation PGException ++ exceptionWithClass: (Class)class + connection: (PGConnection*)connection +{ + return [[[self alloc] initWithClass: class + connection: connection] autorelease]; +} + +- initWithClass: (Class)class_ + connection: (PGConnection*)connection_ +{ + self = [super initWithClass: class_]; + + connection = [connection_ retain]; + + return self; +} + +- (void)dealloc +{ + [connection release]; + + [super dealloc]; +} + +- (OFString*)description +{ + if (description != nil) + return description; + + description = [[OFString alloc] initWithFormat: + @"A PostgreSQL operation in class %@ failed: %s", inClass, + PQerrorMessage([connection PG_connection])]; + + return description; +} + +- (PGConnection*)connection +{ + OF_GETTER(connection, NO) +} +@end diff --git a/test.m b/test.m new file mode 100644 index 0000000..2fab746 --- /dev/null +++ b/test.m @@ -0,0 +1,44 @@ +#import + +#import "PGConnection.h" +#import "PGConnectionFailedException.h" + +@interface Test: OFObject +{ + PGConnection *connection; +} +@end + +OF_APPLICATION_DELEGATE(Test) + +@implementation Test +- (void)applicationDidFinishLaunching +{ + PGResult *result; + + connection = [[PGConnection alloc] init]; + [connection setParameters: + [OFDictionary dictionaryWithKeysAndObjects: @"user", @"js", + @"dbname", @"js", nil]]; + [connection connect]; + + [connection executeCommand: @"DROP TABLE IF EXISTS test"]; + [connection executeCommand: @"CREATE TABLE test (" + @" id integer," + @" name varchar(255)," + @" content text" + @")"]; + [connection executeCommand: @"INSERT INTO test (id, name, content) " + @"VALUES($1, $2, $3)" + parameters: @[@"1", @"foo", @"Hallo Welt!"]]; + [connection executeCommand: @"INSERT INTO test (id, name, content) " + @"VALUES($1, $2, $3)" + parameters: @[@"2", @"bla", @"Blup!!"]]; + + result = [connection executeCommand: @"SELECT * FROM test"]; + of_log(@"%@", result); + of_log(@"JSON: %@", [result JSONRepresentation]); + + [OFApplication terminate]; +} +@end