From 4dde112af720dc60868bb0d6eb4a33f7cda2e100 Mon Sep 17 00:00:00 2001 From: timtaplin Date: Tue, 9 Aug 2016 06:04:58 -0600 Subject: [PATCH 1/7] provide PGResultSet and PGRow classes for row based access to query results and iterable behavior. --- Sources/PostgreSQL/PostgreSQL.swift | 97 ++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/Sources/PostgreSQL/PostgreSQL.swift b/Sources/PostgreSQL/PostgreSQL.swift index 3a5ae9d..a71d034 100644 --- a/Sources/PostgreSQL/PostgreSQL.swift +++ b/Sources/PostgreSQL/PostgreSQL.swift @@ -305,8 +305,101 @@ public final class PGConnection { } +/// Wraps PGResult in an iterable object that also has subscript access to individual rows +class PGResultSet: Sequence, IteratorProtocol { + var count:Int + let res :PGResult + + /// Pass in a PGResult to get access to Sequence and IteratorProtocol conformance, use of for loops, and subscript access to rows by index + init(res:PGResult) { + self.res = res + count = 0 + } + ///provides basic index based retrieval of rows in result set + func getRow(rowIndex: Int) -> PGRow? { + + return PGRow(fromResultSet: self, row: rowIndex) + } + + ///returns next row in the result set. Required for Sequence and IteratorProtocol conformance. Allows use of for - in syntax without having to iterate thru a range of index numbers + public func next() -> PGRow? { + if (count == res.numTuples()) { + return nil + } else { + defer { count += 1} + return PGRow(fromResultSet: self, row: count) + } + } + /// returns specified row by index + public subscript(rowIndex: Int) -> PGRow? { + return getRow(rowIndex: rowIndex) + + } +} - - +///Provides Sequence and Iterator access to the row data from a PGResultSet +class PGRow: Sequence, IteratorProtocol { + var rowPosition:Int + let row:Int + let res:PGResult + var fields = [String:Any?]() + + ///access fields from a specified row in PGResultSet + init(fromResultSet set: PGResultSet, row:Int){ + self.res = set.res + self.row = row + rowPosition = 0 + + while let f = self.next() { + + if(res.fieldIsNull(tupleIndex: self.row, fieldIndex: rowPosition-1)) { + fields[f.0] = nil + } else { + fields[f.0] = f.2 + } + + } + } + + ///Returns a Tuple made up of (fieldName:String, fieldType:Int, fieldValue:Any?) for a field specified by index. This method attempts to return proper type thru use of fieldType Integer, but needs a more complete reference to the field type list to be complete + func getFieldTuple(fieldIndex: Int)-> (String, Int, Any?)? { + if(res.fieldIsNull(tupleIndex: row, fieldIndex: fieldIndex)) { + return (res.fieldName(index: rowPosition)!, Int(res.fieldType(index: fieldIndex)!), nil) + } else { + let fieldtype = Int(res.fieldType(index: fieldIndex)!) + switch fieldtype { + case 23: + return (res.fieldName(index: fieldIndex)!, Int(res.fieldType(index: fieldIndex)!), res.getFieldInt(tupleIndex: row, fieldIndex: fieldIndex)) + case 16: + return (res.fieldName(index: fieldIndex)!, Int(res.fieldType(index: fieldIndex)!), res.getFieldBool(tupleIndex: row, fieldIndex: fieldIndex)) + default: + return (res.fieldName(index: fieldIndex)!, Int(res.fieldType(index: fieldIndex)!), res.getFieldString(tupleIndex: row, fieldIndex: fieldIndex)) + } + + } + } + ///returns next field in the row. Required for Sequence and IteratorProtocol conformance. Allows use of for - in syntax without having to iterate thru a range of index numbers + public func next() -> (String,Int,Any?)? { + let curIndex = rowPosition + if (curIndex >= res.numFields()) { + return nil + } else { + rowPosition += 1 + return getFieldTuple(fieldIndex: curIndex) + } + } + + /// subscript by field Index, returns field Tuple + public subscript(fieldIndex: Int) -> (String,Int,Any?)? { + return getFieldTuple(fieldIndex: fieldIndex) + + } + + /// subscript by field Name, returns field Tuple + public subscript(fieldName: String) -> Any? { + return fields[fieldName] + + } +} From 0ad519008ff0c709d1628391ee22d2cd976c5df7 Mon Sep 17 00:00:00 2001 From: timtaplin Date: Mon, 29 Aug 2016 08:58:09 -0600 Subject: [PATCH 2/7] move PGResultSet methods into PGResult so that the usage is less convoluted. Add Tests --- Sources/PostgreSQL/PostgreSQL.swift | 83 ++++++++++++++++----- Tests/PostgreSQLTests/PostgreSQLTests.swift | 51 +++++++++++++ 2 files changed, 116 insertions(+), 18 deletions(-) diff --git a/Sources/PostgreSQL/PostgreSQL.swift b/Sources/PostgreSQL/PostgreSQL.swift index a71d034..0d49b22 100644 --- a/Sources/PostgreSQL/PostgreSQL.swift +++ b/Sources/PostgreSQL/PostgreSQL.swift @@ -21,7 +21,7 @@ import libpq /// result object -public final class PGResult { +public final class PGResult: Sequence, IteratorProtocol { /// Result Status enum public enum StatusType { @@ -34,7 +34,7 @@ public final class PGResult { case singleTuple case unknown } - + var count:Int = 0 var res: OpaquePointer? = OpaquePointer(bitPattern: 0) init(_ res: OpaquePointer?) { @@ -218,6 +218,29 @@ public final class PGResult { } return ret } + + ///provides basic index based retrieval of rows in result set + public func getRow(_ rowIndex: Int) -> PGRow? { + + return PGRow(fromResult: self, row: rowIndex) + } + + ///returns next row in the result set. Required for Sequence and IteratorProtocol conformance. Allows use of for - in syntax without having to iterate thru a range of index numbers + public func next() -> PGRow? { + if (count == self.numTuples()) { + return nil + } else { + defer { count += 1} + return PGRow(fromResult: self, row: count) + } + } + + /// returns specified row by index + public subscript(rowIndex: Int) -> PGRow? { + return getRow(rowIndex) + + } + } /// connection management class @@ -304,10 +327,10 @@ public final class PGConnection { } } - +/* /// Wraps PGResult in an iterable object that also has subscript access to individual rows -class PGResultSet: Sequence, IteratorProtocol { - var count:Int +class PGResult: Sequence, IteratorProtocol { + var count:Int let res :PGResult /// Pass in a PGResult to get access to Sequence and IteratorProtocol conformance, use of for loops, and subscript access to rows by index @@ -315,14 +338,15 @@ class PGResultSet: Sequence, IteratorProtocol { self.res = res count = 0 } + ///provides basic index based retrieval of rows in result set - func getRow(rowIndex: Int) -> PGRow? { + func getRow(_ rowIndex: Int) -> PGRow? { return PGRow(fromResultSet: self, row: rowIndex) } ///returns next row in the result set. Required for Sequence and IteratorProtocol conformance. Allows use of for - in syntax without having to iterate thru a range of index numbers - public func next() -> PGRow? { + func next() -> PGRow? { if (count == res.numTuples()) { return nil } else { @@ -332,21 +356,22 @@ class PGResultSet: Sequence, IteratorProtocol { } /// returns specified row by index public subscript(rowIndex: Int) -> PGRow? { - return getRow(rowIndex: rowIndex) + return getRow(rowIndex) } } - +*/ ///Provides Sequence and Iterator access to the row data from a PGResultSet -class PGRow: Sequence, IteratorProtocol { +public struct PGRow: Sequence, IteratorProtocol { + var rowPosition:Int let row:Int let res:PGResult var fields = [String:Any?]() ///access fields from a specified row in PGResultSet - init(fromResultSet set: PGResultSet, row:Int){ - self.res = set.res + init(fromResult set: PGResult, row:Int){ + self.res = set self.row = row rowPosition = 0 @@ -362,36 +387,58 @@ class PGRow: Sequence, IteratorProtocol { } ///Returns a Tuple made up of (fieldName:String, fieldType:Int, fieldValue:Any?) for a field specified by index. This method attempts to return proper type thru use of fieldType Integer, but needs a more complete reference to the field type list to be complete - func getFieldTuple(fieldIndex: Int)-> (String, Int, Any?)? { + func getFieldTuple(_ fieldIndex: Int)-> (String, Int, Any?) { if(res.fieldIsNull(tupleIndex: row, fieldIndex: fieldIndex)) { return (res.fieldName(index: rowPosition)!, Int(res.fieldType(index: fieldIndex)!), nil) } else { let fieldtype = Int(res.fieldType(index: fieldIndex)!) switch fieldtype { - case 23: + case 700, 701: + return (res.fieldName(index: fieldIndex)!, Int(res.fieldType(index: fieldIndex)!), res.getFieldFloat(tupleIndex: row, fieldIndex: fieldIndex)) + case 21, 22, 23, 26, 27, 28, 29, 30: return (res.fieldName(index: fieldIndex)!, Int(res.fieldType(index: fieldIndex)!), res.getFieldInt(tupleIndex: row, fieldIndex: fieldIndex)) + case 20: + return (res.fieldName(index: fieldIndex)!, Int(res.fieldType(index: fieldIndex)!), res.getFieldInt8(tupleIndex: row, fieldIndex: fieldIndex)) + case 16: return (res.fieldName(index: fieldIndex)!, Int(res.fieldType(index: fieldIndex)!), res.getFieldBool(tupleIndex: row, fieldIndex: fieldIndex)) default: - return (res.fieldName(index: fieldIndex)!, Int(res.fieldType(index: fieldIndex)!), res.getFieldString(tupleIndex: row, fieldIndex: fieldIndex)) + if let fieldString = res.getFieldString(tupleIndex: row, fieldIndex:fieldIndex) { + return (res.fieldName(index: fieldIndex)!, Int(res.fieldType(index: fieldIndex)!), fieldString) + } else { + return (res.fieldName(index: fieldIndex)!, Int(res.fieldType(index: fieldIndex)!), "") + } } } } + + func getFieldValue(_ fieldIndex: Int) -> Any? { + return getFieldTuple(fieldIndex).2 + } + + func getFieldName(_ fieldIndex: Int) -> String? { + return res.fieldName(index: fieldIndex) + } + + func getFieldType(_ fieldIndex: Int) -> Int? { + return Int(res.fieldType(index: fieldIndex)!) + } + ///returns next field in the row. Required for Sequence and IteratorProtocol conformance. Allows use of for - in syntax without having to iterate thru a range of index numbers - public func next() -> (String,Int,Any?)? { + public mutating func next() -> (String,Int,Any?)? { let curIndex = rowPosition if (curIndex >= res.numFields()) { return nil } else { rowPosition += 1 - return getFieldTuple(fieldIndex: curIndex) + return getFieldTuple(curIndex) } } /// subscript by field Index, returns field Tuple public subscript(fieldIndex: Int) -> (String,Int,Any?)? { - return getFieldTuple(fieldIndex: fieldIndex) + return getFieldTuple(fieldIndex) } diff --git a/Tests/PostgreSQLTests/PostgreSQLTests.swift b/Tests/PostgreSQLTests/PostgreSQLTests.swift index b085f44..9269ba4 100644 --- a/Tests/PostgreSQLTests/PostgreSQLTests.swift +++ b/Tests/PostgreSQLTests/PostgreSQLTests.swift @@ -105,6 +105,57 @@ class PostgreSQLTests: XCTestCase { res.clear() p.finish() } + + func testExecGetRow() { + let p = PGConnection() + let status = p.connectdb(postgresTestConnInfo) + XCTAssert(status == .ok) + // name, oid, integer, boolean + let res = p.exec(statement: "select datname,datdba,encoding,datistemplate from pg_database") + XCTAssertEqual(res.status(), PGResult.StatusType.tuplesOK) + + let num = res.numTuples() + XCTAssert(num > 0) + for x in 0.. 0) + let c2 = row.getFieldTuple(1).2 as? Int + let c3 = row.getFieldTuple(2).2 as? Int + let c4 = row.getFieldTuple(3).2 as? Bool + print("c1=\(c1) c2=\(c2) c3=\(c3) c4=\(c4)") + + } + } + res.clear() + p.finish() + } + + func testExecGetRowAgain() { + let p = PGConnection() + let status = p.connectdb(postgresTestConnInfo) + XCTAssert(status == .ok) + // name, oid, integer, boolean + let res = p.exec(statement: "select datname,datdba,encoding,datistemplate from pg_database") + XCTAssertEqual(res.status(), PGResult.StatusType.tuplesOK) + + let num = res.numTuples() + XCTAssert(num > 0) + + while let row = res.next() { + + let c1 = row.getFieldValue(0) as? String + XCTAssertTrue((c1?.characters.count)! > 0) + let c2 = row.getFieldValue(1) as? Int + let c3 = row.getFieldValue(2) as? Int + let c4 = row.getFieldValue(3) as? Bool + print("c1=\(c1) c2=\(c2) c3=\(c3) c4=\(c4)") + + } + res.clear() + p.finish() + } + } extension PostgreSQLTests { From 00729a1cbf0c67fc9cd923022fe48e51ea15de30 Mon Sep 17 00:00:00 2001 From: timtaplin Date: Tue, 30 Aug 2016 08:40:42 -0600 Subject: [PATCH 3/7] added tests for use case of subscript by field name and subscript by field index, updated subscript code to return just field value for subscript by index --- Sources/PostgreSQL/PostgreSQL.swift | 8 ++-- Tests/PostgreSQLTests/PostgreSQLTests.swift | 49 +++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Sources/PostgreSQL/PostgreSQL.swift b/Sources/PostgreSQL/PostgreSQL.swift index 0d49b22..2313c0a 100644 --- a/Sources/PostgreSQL/PostgreSQL.swift +++ b/Sources/PostgreSQL/PostgreSQL.swift @@ -436,13 +436,13 @@ public struct PGRow: Sequence, IteratorProtocol { } } - /// subscript by field Index, returns field Tuple - public subscript(fieldIndex: Int) -> (String,Int,Any?)? { - return getFieldTuple(fieldIndex) + /// subscript by field Index, returns field value + public subscript(fieldIndex: Int) -> Any? { + return getFieldValue(fieldIndex) } - /// subscript by field Name, returns field Tuple + /// subscript by field Name, returns field value public subscript(fieldName: String) -> Any? { return fields[fieldName] diff --git a/Tests/PostgreSQLTests/PostgreSQLTests.swift b/Tests/PostgreSQLTests/PostgreSQLTests.swift index 9269ba4..dc53e33 100644 --- a/Tests/PostgreSQLTests/PostgreSQLTests.swift +++ b/Tests/PostgreSQLTests/PostgreSQLTests.swift @@ -156,6 +156,55 @@ class PostgreSQLTests: XCTestCase { p.finish() } + func testExecUseFieldNameSubscript() { + let p = PGConnection() + let status = p.connectdb(postgresTestConnInfo) + XCTAssert(status == .ok) + // name, oid, integer, boolean + let res = p.exec(statement: "select datname,datdba,encoding,datistemplate from pg_database") + XCTAssertEqual(res.status(), PGResult.StatusType.tuplesOK) + + let num = res.numTuples() + XCTAssert(num > 0) + + while let row = res.next() { + + let c1 = row["datname"] as? String + XCTAssertTrue((c1?.characters.count)! > 0) + let c2 = row["datdba"] as? Int + let c3 = row["encoding"] as? Int + let c4 = row["datistemplate"] as? Bool + print("c1=\(c1) c2=\(c2) c3=\(c3) c4=\(c4)") + + } + res.clear() + p.finish() + } + + func testExecUseFieldIndexSubscript() { + let p = PGConnection() + let status = p.connectdb(postgresTestConnInfo) + XCTAssert(status == .ok) + // name, oid, integer, boolean + let res = p.exec(statement: "select datname,datdba,encoding,datistemplate from pg_database") + XCTAssertEqual(res.status(), PGResult.StatusType.tuplesOK) + + let num = res.numTuples() + XCTAssert(num > 0) + + while let row = res.next() { + + let c1 = row[0] as? String + XCTAssertTrue((c1?.characters.count)! > 0) + let c2 = row[1] as? Int + let c3 = row[2] as? Int + let c4 = row[3] as? Bool + print("c1=\(c1) c2=\(c2) c3=\(c3) c4=\(c4)") + + } + res.clear() + p.finish() + } } extension PostgreSQLTests { From bc41590204849cae1f112cf5f4a358e050d8eba2 Mon Sep 17 00:00:00 2001 From: timtaplin Date: Tue, 30 Aug 2016 10:55:59 -0600 Subject: [PATCH 4/7] clean up tests, add counter reset in next functions so the result set or row can be accessed multiple times --- Sources/PostgreSQL/PostgreSQL.swift | 40 +++------------------ Tests/PostgreSQLTests/PostgreSQLTests.swift | 6 +++- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/Sources/PostgreSQL/PostgreSQL.swift b/Sources/PostgreSQL/PostgreSQL.swift index 2313c0a..b2856ae 100644 --- a/Sources/PostgreSQL/PostgreSQL.swift +++ b/Sources/PostgreSQL/PostgreSQL.swift @@ -228,6 +228,7 @@ public final class PGResult: Sequence, IteratorProtocol { ///returns next row in the result set. Required for Sequence and IteratorProtocol conformance. Allows use of for - in syntax without having to iterate thru a range of index numbers public func next() -> PGRow? { if (count == self.numTuples()) { + count = 0 return nil } else { defer { count += 1} @@ -327,41 +328,7 @@ public final class PGConnection { } } -/* -/// Wraps PGResult in an iterable object that also has subscript access to individual rows -class PGResult: Sequence, IteratorProtocol { - var count:Int - let res :PGResult - - /// Pass in a PGResult to get access to Sequence and IteratorProtocol conformance, use of for loops, and subscript access to rows by index - init(res:PGResult) { - self.res = res - count = 0 - } - - ///provides basic index based retrieval of rows in result set - func getRow(_ rowIndex: Int) -> PGRow? { - - return PGRow(fromResultSet: self, row: rowIndex) - } - - ///returns next row in the result set. Required for Sequence and IteratorProtocol conformance. Allows use of for - in syntax without having to iterate thru a range of index numbers - func next() -> PGRow? { - if (count == res.numTuples()) { - return nil - } else { - defer { count += 1} - return PGRow(fromResultSet: self, row: count) - } - } - /// returns specified row by index - public subscript(rowIndex: Int) -> PGRow? { - return getRow(rowIndex) - - } -} -*/ -///Provides Sequence and Iterator access to the row data from a PGResultSet +///Provides Sequence and Iterator access to the row data from a PGResult public struct PGRow: Sequence, IteratorProtocol { var rowPosition:Int @@ -369,7 +336,7 @@ public struct PGRow: Sequence, IteratorProtocol { let res:PGResult var fields = [String:Any?]() - ///access fields from a specified row in PGResultSet + ///access fields from a specified row in PGResult init(fromResult set: PGResult, row:Int){ self.res = set self.row = row @@ -429,6 +396,7 @@ public struct PGRow: Sequence, IteratorProtocol { public mutating func next() -> (String,Int,Any?)? { let curIndex = rowPosition if (curIndex >= res.numFields()) { + rowPosition = 0 return nil } else { rowPosition += 1 diff --git a/Tests/PostgreSQLTests/PostgreSQLTests.swift b/Tests/PostgreSQLTests/PostgreSQLTests.swift index dc53e33..65a5bc9 100644 --- a/Tests/PostgreSQLTests/PostgreSQLTests.swift +++ b/Tests/PostgreSQLTests/PostgreSQLTests.swift @@ -213,7 +213,11 @@ extension PostgreSQLTests { ("testConnect", testConnect), ("testExec", testExec), ("testExecGetValues", testExecGetValues), - ("testExecGetValuesParams", testExecGetValuesParams) + ("testExecGetValuesParams", testExecGetValuesParams), + ("testExecGetRow", testExecGetRow), + ("testExecGetRowAgain", testExecGetRowAgain), + ("testExecUseFieldNameSubscript", testExecUseFieldNameSubscript), + ("testExecUseFieldIndexSubscript", testExecUseFieldIndexSubscript) ] } } From d01e343e3437f1772285090a1ec6f5c07be62005 Mon Sep 17 00:00:00 2001 From: Tim Taplin Date: Tue, 30 Aug 2016 10:56:42 -0600 Subject: [PATCH 5/7] started adding quickstart details --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/README.md b/README.md index 07c429a..ec3227a 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,48 @@ Add this project as a dependency in your Package.swift file. ``` .Package(url: "https://github.com/PerfectlySoft/Perfect-PostgreSQL.git", versions: Version(0,0,0).. PGResult? { + + // need to make sure something is available. + guard dataPG.connectdb(postgresTestConnInfo) == PGConnection.StatusType.ok else { + Log.info(message: "Failure connecting to data server \(postgresTestConnInfo)") + + return nil + } + + defer { + dataPG.close() // defer ensures we close our db connection at the end of this request + } + // setup basic query + //retrieving fields with type name, oid, integer, boolean + let queryResult:PGResult = dataPG.exec(statement: "select datname,datdba,encoding,datistemplate from pg_database") + + defer { queryResult.clear() } + return queryResult +} +``` + +Use the queryResult to access the row data using PGRow + + +Additionally, there are more complex Statement constructors, and potential object designs which can further abstract the process of interacting with your data. From 897e360c4e366180702a180aa56fd0ba96270506 Mon Sep 17 00:00:00 2001 From: Tim Taplin Date: Tue, 30 Aug 2016 11:57:20 -0600 Subject: [PATCH 6/7] add row based access examples --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index ec3227a..c5645c1 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,50 @@ public func usePostgres() -> PGResult? { ``` Use the queryResult to access the row data using PGRow +Here is a function that uses several different methods to view the row contents + +```swift +public func useRows(result: PGResult?) { + //get rows + guard result != nil else { + return + } + + for row:PGRow in result! { + for f in row { + //print field tuples + //this returns a tuple (fieldName:String, fieldType:Int, fieldValue:Any?) + print("Field: \(f)") + } + + } + + for row:PGRow in result! { + + //raw description + Log.info(message: "Row description: \(row)") + + //retrieve field values by name + Log.info(message: "row( datname: \(row["datname"]), datdba: \(row["datdba"]), encoding: \(row["encoding"]), datistemplate: \(row["datistemplate"])") + + //retrieve field values by index + Log.info(message: "row( datname: \(row[0]), datdba: \(row[1]), encoding: \(row[2]), datistemplate: \(row[3])") + + // field values are properly typed, but you have to cast to tell the compiler what we have + let c1 = row["datname"] as? String + let c2 = row["datdba"] as? Int + let c3 = row["encoding"] as? Int + let c4 = row["datistemplate"] as? Bool + print("c1=\(c1) c2=\(c2) c3=\(c3) c4=\(c4)") + + } +} +``` + +Rows can also be accessed by index using subscript syntax: +```swift + let secondRow = result[1] +``` Additionally, there are more complex Statement constructors, and potential object designs which can further abstract the process of interacting with your data. From e6efd5804a86a36dde2b2257c398b9c7c25bea64 Mon Sep 17 00:00:00 2001 From: Tim Taplin Date: Tue, 30 Aug 2016 11:58:23 -0600 Subject: [PATCH 7/7] add swift reference to codeblocks --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c5645c1..c039f57 100644 --- a/README.md +++ b/README.md @@ -70,12 +70,12 @@ Add this project as a dependency in your Package.swift file. Add a file to your project, making sure that it is stored in the Sources directory of your file structure. Lets name it pg_quickstart.swift for example. Import required libraries: -``` +```swift import PostgreSQL ``` Setup the credentials for your connection: -``` +```swift let postgresTestConnInfo = "host=localhost dbname=postgres" let dataPG = PGConnection() @@ -83,7 +83,7 @@ let dataPG = PGConnection() This function will setup and use a PGConnection -``` +```swift public func usePostgres() -> PGResult? { // need to make sure something is available.