-
Notifications
You must be signed in to change notification settings - Fork 34
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
Fix for Issue 23 #45
Fix for Issue 23 #45
Changes from all commits
d6f7060
559f107
255594b
605947e
7b01f2d
9365b6b
c744491
9559614
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,18 +17,27 @@ | |
import SwiftKuery | ||
import CLibpq | ||
|
||
import Dispatch | ||
import Foundation | ||
|
||
enum ConnectionState { | ||
case idle, runningQuery, fetchingResultSet | ||
} | ||
|
||
// MARK: PostgreSQLConnection | ||
|
||
/// An implementation of `SwiftKuery.Connection` protocol for PostgreSQL. | ||
/// Please see [PostgreSQL manual](https://www.postgresql.org/docs/8.0/static/libpq-exec.html) for details. | ||
public class PostgreSQLConnection: Connection { | ||
|
||
private var connection: OpaquePointer? | ||
var connection: OpaquePointer? | ||
private var connectionParameters: String = "" | ||
private var inTransaction = false | ||
|
||
private var state: ConnectionState = .idle | ||
private var stateLock = DispatchSemaphore(value: 1) | ||
private weak var currentResultFetcher: PostgreSQLResultFetcher? | ||
|
||
private var preparedStatements = Set<String>() | ||
|
||
/// An indication whether there is a connection to the database. | ||
|
@@ -286,16 +295,21 @@ public class PostgreSQLConnection: Connection { | |
} | ||
|
||
private func prepareStatement(name: String, for query: String) -> String? { | ||
if let error = setUpForRunningQuery() { | ||
return error | ||
} | ||
let result = PQprepare(connection, name, query, 0, nil) | ||
let status = PQresultStatus(result) | ||
if status != PGRES_COMMAND_OK { | ||
var errorMessage = "Failed to create prepared statement." | ||
if let error = String(validatingUTF8: PQerrorMessage(connection)) { | ||
errorMessage += " Error: \(error)." | ||
} | ||
PQclear(result) | ||
return errorMessage | ||
setState(.idle) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since |
||
var errorMessage = "Failed to create prepared statement." | ||
if let error = String(validatingUTF8: PQerrorMessage(connection)) { | ||
errorMessage += " Error: \(error)." | ||
} | ||
PQclear(result) | ||
return errorMessage | ||
} | ||
setState(.idle) | ||
PQclear(result) | ||
preparedStatements.insert(name) | ||
return nil | ||
|
@@ -342,6 +356,11 @@ public class PostgreSQLConnection: Connection { | |
return | ||
} | ||
|
||
if let error = setUpForRunningQuery() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naming this |
||
onCompletion(.error(QueryError.connection(error))) | ||
return | ||
} | ||
|
||
var parameterPointers = [UnsafeMutablePointer<Int8>?]() | ||
var parameterData = [UnsafePointer<Int8>?]() | ||
// At the moment we only create string parameters. Binary parameters should be added. | ||
|
@@ -391,6 +410,7 @@ public class PostgreSQLConnection: Connection { | |
|
||
private func processQueryResult(query: String, onCompletion: @escaping ((QueryResult) -> ())) { | ||
guard let result = PQgetResult(connection) else { | ||
setState(.idle) | ||
var errorMessage = "No result returned for query: \(query)." | ||
if let error = String(validatingUTF8: PQerrorMessage(connection)) { | ||
errorMessage += " Error: \(error)." | ||
|
@@ -403,16 +423,18 @@ public class PostgreSQLConnection: Connection { | |
if status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK { | ||
// Since we set the single row mode, PGRES_TUPLES_OK means the result is empty, i.e. there are | ||
// no rows to return. | ||
clearResult(result, connection: connection) | ||
clearResult(result, connection: self) | ||
onCompletion(.successNoData) | ||
} | ||
else if status == PGRES_SINGLE_TUPLE { | ||
let resultFetcher = PostgreSQLResultFetcher(queryResult: result, connection: connection) | ||
let resultFetcher = PostgreSQLResultFetcher(queryResult: result, connection: self) | ||
setState(.fetchingResultSet) | ||
currentResultFetcher = resultFetcher | ||
onCompletion(.resultSet(ResultSet(resultFetcher))) | ||
} | ||
else { | ||
let errorMessage = String(validatingUTF8: PQresultErrorMessage(result)) ?? "Unknown" | ||
clearResult(result, connection: connection) | ||
clearResult(result, connection: self) | ||
onCompletion(.error(QueryError.databaseError("Query execution error:\n" + errorMessage + " For query: " + query))) | ||
} | ||
} | ||
|
@@ -475,6 +497,11 @@ public class PostgreSQLConnection: Connection { | |
return | ||
} | ||
|
||
if let error = setUpForRunningQuery() { | ||
onCompletion(.error(QueryError.connection(error))) | ||
return | ||
} | ||
|
||
let result = PQexec(connection, command) | ||
let status = PQresultStatus(result) | ||
if status != PGRES_COMMAND_OK { | ||
|
@@ -484,6 +511,7 @@ public class PostgreSQLConnection: Connection { | |
} | ||
|
||
PQclear(result) | ||
setState(.idle) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as above - could move single |
||
onCompletion(.error(QueryError.databaseError(message))) | ||
return | ||
} | ||
|
@@ -493,6 +521,7 @@ public class PostgreSQLConnection: Connection { | |
} | ||
|
||
PQclear(result) | ||
setState(.idle) | ||
onCompletion(.successNoData) | ||
} | ||
|
||
|
@@ -513,4 +542,46 @@ public class PostgreSQLConnection: Connection { | |
} | ||
return postgresQuery | ||
} | ||
|
||
private func lockStateLock() { | ||
_ = stateLock.wait(timeout: DispatchTime.distantFuture) | ||
} | ||
|
||
private func unlockStateLock() { | ||
stateLock.signal() | ||
} | ||
|
||
func setState(_ newState: ConnectionState) { | ||
lockStateLock() | ||
if state == .fetchingResultSet { | ||
currentResultFetcher = nil | ||
} | ||
state = newState | ||
unlockStateLock() | ||
} | ||
|
||
func setUpForRunningQuery() -> String? { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is weird, a setup function which returns an optional String? Why not just make it |
||
lockStateLock() | ||
|
||
switch state { | ||
case .runningQuery: | ||
unlockStateLock() | ||
return "The connection is in the middle of running a query" | ||
|
||
case .fetchingResultSet: | ||
currentResultFetcher?.hasMoreRows = false | ||
unlockStateLock() | ||
clearResult(nil, connection: self) | ||
lockStateLock() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there's a potential problem here. Another thread could get in to |
||
|
||
case .idle: | ||
break | ||
} | ||
|
||
state = .runningQuery | ||
|
||
unlockStateLock() | ||
|
||
return nil | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be better as a nested enum inside
PostgreSQLConnection
, calledState
.