diff --git a/README.md b/README.md index 07c429a..c039f57 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,92 @@ 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 +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. diff --git a/Sources/PostgreSQL/PostgreSQL.swift b/Sources/PostgreSQL/PostgreSQL.swift index 3a5ae9d..b2856ae 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,30 @@ 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()) { + count = 0 + 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,9 +328,93 @@ public final class PGConnection { } } - - - - +///Provides Sequence and Iterator access to the row data from a PGResult +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 PGResult + init(fromResult set: PGResult, row:Int){ + self.res = set + 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 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: + 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 mutating func next() -> (String,Int,Any?)? { + let curIndex = rowPosition + if (curIndex >= res.numFields()) { + rowPosition = 0 + return nil + } else { + rowPosition += 1 + return getFieldTuple(curIndex) + } + } + + /// subscript by field Index, returns field value + public subscript(fieldIndex: Int) -> Any? { + return getFieldValue(fieldIndex) + + } + + /// 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 b085f44..65a5bc9 100644 --- a/Tests/PostgreSQLTests/PostgreSQLTests.swift +++ b/Tests/PostgreSQLTests/PostgreSQLTests.swift @@ -105,6 +105,106 @@ 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() + } + + 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 { @@ -113,7 +213,11 @@ extension PostgreSQLTests { ("testConnect", testConnect), ("testExec", testExec), ("testExecGetValues", testExecGetValues), - ("testExecGetValuesParams", testExecGetValuesParams) + ("testExecGetValuesParams", testExecGetValuesParams), + ("testExecGetRow", testExecGetRow), + ("testExecGetRowAgain", testExecGetRowAgain), + ("testExecUseFieldNameSubscript", testExecUseFieldNameSubscript), + ("testExecUseFieldIndexSubscript", testExecUseFieldIndexSubscript) ] } }