Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update PGResult and create PGRow class in the PostgreSQL library, plus quickstart #7

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)..<Version(10,0,0))
```

## QuickStart

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()
```

This function will setup and use a PGConnection

```swift
public func usePostgres() -> 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)")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use of unresolved identifier 'Log'

This doesn't compile for me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, the Log struct is in the PerfectLib, I may not have specified that as an expected dependency. You could use print("Row description: (row)") if you dont want to import PerfectLib. I get so used to having the many Perfect utilities available that I dont work without it.


//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.
120 changes: 114 additions & 6 deletions Sources/PostgreSQL/PostgreSQL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import libpq

/// result object
public final class PGResult {
public final class PGResult: Sequence, IteratorProtocol {

/// Result Status enum
public enum StatusType {
Expand All @@ -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?) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]

}
}


106 changes: 105 additions & 1 deletion Tests/PostgreSQLTests/PostgreSQLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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..<num {
if let row = res.getRow(x) {
let c1 = row.getFieldTuple(0).2 as? String
XCTAssertTrue((c1?.characters.count)! > 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 {
Expand All @@ -113,7 +213,11 @@ extension PostgreSQLTests {
("testConnect", testConnect),
("testExec", testExec),
("testExecGetValues", testExecGetValues),
("testExecGetValuesParams", testExecGetValuesParams)
("testExecGetValuesParams", testExecGetValuesParams),
("testExecGetRow", testExecGetRow),
("testExecGetRowAgain", testExecGetRowAgain),
("testExecUseFieldNameSubscript", testExecUseFieldNameSubscript),
("testExecUseFieldIndexSubscript", testExecUseFieldIndexSubscript)
]
}
}
Expand Down