Skip to content

Commit a710c57

Browse files
committed
Added a method which tests a database connection before initiation.
This method raises a SQLClient connection error if no server is present at the location specified. This is a stand in for microsoft's own response to an invalid connection string. This also allows the tests to test with a configuration which will report failure after 5 seconds of attempts.
1 parent 6be7100 commit a710c57

1 file changed

Lines changed: 47 additions & 0 deletions

File tree

Sources/SQLClientSwift/SQLClient.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,54 @@ public actor SQLClient {
192192

193193
// MARK: - Synchronous Helpers
194194

195+
private nonisolated func checkReachability(server: String, port: UInt16) throws {
196+
let host = CFHostCreateWithName(nil, server as CFString).takeRetainedValue()
197+
var error = CFStreamError()
198+
CFHostStartInfoResolution(host, .addresses, &error)
199+
200+
guard error.error == 0 else {
201+
throw SQLClientError.connectionFailed(server: server)
202+
}
203+
204+
// Attempt a TCP connection with a 5 second timeout
205+
var readStream: Unmanaged<CFReadStream>?
206+
var writeStream: Unmanaged<CFWriteStream>?
207+
CFStreamCreatePairWithSocketToHost(nil, server as CFString, UInt32(port), &readStream, &writeStream)
208+
209+
guard let read = readStream?.takeRetainedValue(),
210+
let write = writeStream?.takeRetainedValue() else {
211+
throw SQLClientError.connectionFailed(server: server)
212+
}
213+
214+
CFReadStreamOpen(read)
215+
CFWriteStreamOpen(write)
216+
217+
let deadline = Date().addingTimeInterval(5)
218+
var connected = false
219+
220+
while Date() < deadline {
221+
let readStatus = CFReadStreamGetStatus(read)
222+
let writeStatus = CFWriteStreamGetStatus(write)
223+
if readStatus == .open && writeStatus == .open {
224+
connected = true
225+
break
226+
}
227+
Thread.sleep(forTimeInterval: 0.1)
228+
}
229+
230+
CFReadStreamClose(read)
231+
CFWriteStreamClose(write)
232+
233+
guard connected else {
234+
throw SQLClientError.connectionFailed(server: server)
235+
}
236+
}
237+
195238
private nonisolated func _connectSync(options: SQLClientConnectionOptions) throws -> (login: TDSHandle, connection: TDSHandle) {
239+
// Pre-flight — fail fast if the server isn't reachable at the TCP level.
240+
// Default port for SQL Server is 1433.
241+
try checkReachability(server: options.server, port: options.port ?? 1433)
242+
196243
dbinit()
197244
dberrhandle(SQLClient_errorHandler)
198245
dbmsghandle(SQLClient_messageHandler)

0 commit comments

Comments
 (0)