Question

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
Was it helpful?

Solution

(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
  //
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top