In case of a connection problem, the dbExpress driver throws a TDBXError but does not include a socket error code. The message is simply:
Unable to complete network request to host "exampledb.local". Failed to establish a connection
Is there a way to retrieve the underlying socket error when this type of exception occurs?
The stack trace is:
main thread ($5934):
0061aa59 +051 example.exe DBXCommon 447 +0 TDBXContext.Error
00817f14 +10c example.exe DBXDynalink 796 +21 TDBXMethodTable.RaiseError
00818553 +013 example.exe DBXDynalink 949 +1 TDBXDynalinkConnection.CheckResult
00818744 +050 example.exe DBXDynalink 1048 +4 TDBXDynalinkConnection.DerivedOpen
0061750f +007 example.exe DBXCommon 447 +0 TDBXConnection.Open
00612fed +0f5 example.exe DBXCommon 447 +0 TDBXConnectionFactory.GetConnection
00612ef1 +005 example.exe DBXCommon 447 +0 TDBXConnectionFactory.GetConnection
0062c08f +26f example.exe SqlExpr TSQLConnection.DoConnect
005d9019 +039 example.exe DB TCustomConnection.SetConnected
005d8fd4 +004 example.exe DB TCustomConnection.Open
0062b98f +01b example.exe SqlExpr TSQLConnection.CheckConnection
0062ebdf +01f example.exe SqlExpr TCustomSQLDataSet.CheckConnection
0062efe4 +04c example.exe SqlExpr TCustomSQLDataSet.OpenCursor
005e91d5 +055 example.exe DB TDataSet.SetActive
005e901c +004 example.exe DB TDataSet.Open
(Delphi XE2 and 'gds32.dll' 10.0.1.335 (ansi) is used in this answer)
dbExpress is a high level framework that delegates provider specific operations to its drivers. All of these provider specific drivers expose the same basic common functionality, hence it is not possible to retrieve specific information which falls outside this common functionality.
Regarding error reporting, the drivers export two functions. Be it the mysql or the mssql or any other driver, these functions are DBXBase_GetErrorMessageLength
and DBXBase_GetErrorMessage
. The drivers get most of what went wrong from client libraries and report that information to the framework through these functions. Beyond what have already reported, it is not possible to get any more specific detail about what went wrong about connecting to a database server or any other operation.
So it is up to the client libraries to include winsock error information or not. In the case of the interbase driver, which the error included in the question is from, this information is already included (when there is one).
Here is an example case, an attempt to connect to an existing server, and to a port that is listened but not by the database server.
Connection := TSQLConnection.Create(nil);
Connection.DriverName := 'Interbase';
Connection.Params.Values['Database'] := 'existingserver/80:database';
try
Connection.Open;
except
on E: TDBxError do begin
writeln(E.Message + sLineBreak);
Here is the output:
Unable to complete network request to host "existingserver:80".
Failed to establish a connection.
As you notice this is exactly the same error message in the question. Note that there is no winsock error in the text. That is because there is no winsock error, as long as the network protocol is concerned, the connection is actually successful.
Here is an attempt to an existing server, but to a non-listened port.
Connection := TSQLConnection.Create(nil);
Connection.DriverName := 'Interbase';
Connection.Params.Values['Database'] := 'existingserver/81:database';
try
Connection.Open;
except
on E: TDBxError do begin
writeln(E.Message + sLineBreak);
Here is the returned error:
Unable to complete network request to host "existingserver:81".
Failed to establish a connection.
unknown Win32 error 10060
This time the connection fails since there is no listener for the specific port. I have no idea about why the client library fails to resolve 10060, but this is your winsock error.
Case for localhost, non-listened port:
Unable to complete network request to host "localhost:3051".
Failed to establish a connection.
No connection could be made because the target machine actively refused it.
Here we have a 10061.
When an attempt to connect to a non-resolvable host is made, gds32.dll does not report any api error. I don't know how the library resolves a host or why it doesn't include error code, but the error message is verbose:
Unable to complete network request to host "nonexistingserver".
Failed to locate host machine.
The specified name was not found in the hosts file or Domain Name Services.
If we were to directly use the client library, we could get only the api error. Examine the following program where a connection attempt is made to an existing server and a closed port.
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
system.sysutils,
data.dbxcommon,
data.sqlexpr,
data.dbxinterbase;
function isc_attach_database(status_vector: PLongint; db_name_length: Smallint;
db_name: PAnsiChar; db_handle: PPointer; parm_buffer_length: Smallint;
parm_buffer: PAnsiChar): Longint; stdcall; external 'gds32.dll';
function isc_interprete(buffer: PAnsiChar; var status_vector: Pointer): Longint;
stdcall; external 'gds32.dll';
var
Connection: TSQLConnection;
StatusVector: array[0..19] of Longint;
Handle: PPointer;
Error: array[0..255] of AnsiChar;
IntrStatus: Pointer;
s: string;
begin
try
Connection := TSQLConnection.Create(nil);
Connection.DriverName := 'Interbase';
Connection.Params.Values['Database'] := 'server/3051:database'; // closed port
try
Connection.Open;
except
on E: TDBxError do begin
writeln(E.Message + sLineBreak);
if E.ErrorCode = TDBXErrorCodes.ConnectionFailed then begin
Handle := nil;
if isc_attach_database(@StatusVector, 0,
PAnsiChar(AnsiString(Connection.Params.Values['Database'])),
@Handle, 0, nil) <> 0 then begin
IntrStatus := @StatusVector;
s := '';
while isc_interprete(Error, IntrStatus) <> 0 do begin
s := s + AnsiString(Error) + sLineBreak;
if PLongint(IntrStatus)^ = 17 then // isc_arg_win32
s := s + ' --below is an api error--' + sLineBreak +
Format('%d: %s', [PLongint(Longint(IntrStatus) + 4)^,
SysErrorMessage(PLongint(Longint(IntrStatus) + 4)^)]) +
sLineBreak;
end;
Writeln(s);
end;
end else
raise;
end;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
Which outputs:
Unable to complete network request to host "sever:3051".
Failed to establish a connection.
unknown Win32 error 10060
Unable to complete network request to host "server:3051".
Failed to establish a connection.
--below is an api error--
10060: A connection attempt failed because the connected party did not properly
respond after a period of time, or established connection failed because connect
ed host has failed to respond
unknown Win32 error 10060
The first paragraph is what dbx reports. The second paragraph is what we get from 'gds32.dll' including the injection of api error code and text, otherwise they are the same.
The above is a crude demonstration, for proper typing use 'interbase.h'. And for details about picking a possible api error, see "Parsing the Status Vector", or in general "Handling Error Conditions".
In any case, as can be seen, being able to get specific information entirely depends on the client library that dbx uses to connect to the database server.
For the general case, to get winsock error information independently from the database server being used, what you can do is to attempt to connect a socket to the server before trying to open a database connection, and only if this is successful close your test connection and then proceed attaching to the database. You can use any library or bare api to do this.
Here is a simple example:
function TestConnect(server: string; port: Integer): Boolean;
procedure WinsockError(step: string);
begin
raise Exception.Create(Format('"%s" fail. %d: %s:',
[step, WSAGetLastError, SysErrorMessage(WSAGetLastError)]));
end;
var
Error: Integer;
Data: TWSAData;
Socket: TSocket;
SockAddr: TSockAddrIn;
Host: PHostEnt;
begin
Result := False;
Error := WSAStartup(MakeWord(1, 1), Data);
if Error = 0 then begin
try
Socket := winsock.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if Socket <> INVALID_SOCKET then begin
try
Host := gethostbyname(PAnsiChar(AnsiString(server)));
if Host <> nil then begin
SockAddr.sin_family := AF_INET;
SockAddr.sin_addr.S_addr := Longint(PLongint(Host^.h_addr_list^)^);
SockAddr.sin_port := htons(port);
if connect(Socket, SockAddr, SizeOf(SockAddr)) <> 0 then
WinsockError('connect')
else
Result := True;
end else
WinsockError('gethostbyname');
finally
closesocket(Socket);
end;
end else
WinsockError('socket');
finally
WSACleanup;
end;
end else
raise Exception.Create('winsock initialization fail');
end;
You can use something like this like:
if TestConnect('server', 3050) then
//