From dbfce3528e370166a78f3edef6f5b62464afa3c4 Mon Sep 17 00:00:00 2001 From: Jonathan Schleifer Date: Wed, 3 Oct 2012 13:20:06 +0000 Subject: [PATCH] Initial import. FossilOrigin-Name: de46b0e10c5d9f516acbbf2ea01d84d0d5ac126412949d459265279262a1b87e --- .gitignore | 5 + Makefile | 30 ++++++ PGConnection.h | 25 +++++ PGConnection.m | 128 +++++++++++++++++++++++ PGResult.h | 13 +++ PGResult.m | 46 ++++++++ PGResultRow.h | 18 ++++ PGResultRow.m | 117 +++++++++++++++++++++ exceptions/PGCommandFailedException.h | 19 ++++ exceptions/PGCommandFailedException.m | 53 ++++++++++ exceptions/PGConnectionFailedException.h | 4 + exceptions/PGConnectionFailedException.m | 21 ++++ exceptions/PGException.h | 19 ++++ exceptions/PGException.m | 44 ++++++++ test.m | 44 ++++++++ 15 files changed, 586 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 PGConnection.h create mode 100644 PGConnection.m create mode 100644 PGResult.h create mode 100644 PGResult.m create mode 100644 PGResultRow.h create mode 100644 PGResultRow.m create mode 100644 exceptions/PGCommandFailedException.h create mode 100644 exceptions/PGCommandFailedException.m create mode 100644 exceptions/PGConnectionFailedException.h create mode 100644 exceptions/PGConnectionFailedException.m create mode 100644 exceptions/PGException.h create mode 100644 exceptions/PGException.m create mode 100644 test.m 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