Watch, Follow, &
Connect with Us
Public Report
Report From: Delphi-BCB/Database/DBExpress/TSQLConnection    [ Add a report in this area ]  
Report #:  58377   Status: Closed
SQLConnection close leads to invalid SQLConnection.FMetaData value
Project:  Delphi Build #:  11.0.2902.10471
Version:    11.2 Submitted By:   Peter Sawatzki
Report Type:  Basic functionality failure Date Reported:  2/17/2008 8:55:35 AM
Severity:    Serious / Highly visible problem Last Updated: 2/16/2010 11:10:05 PM
Platform:    All platforms Internal Tracking #:   263319
Resolution: Cannot Reproduce (Resolution Comments) Resolved in Build: : Delphi 2009
Duplicate of:  None
Voting and Rating
Overall Rating: No Ratings Yet
0.00 out of 5
Total Votes: 1
Description
TSQLConnection.DoDisconnect does not assign nil to FMetaData although the corresponding object instance was already freed in a call to TDBXConnection.Close. This leads to access violations in certain situations and potential problems. See steps for a reproducible case that generates an access violation.
Steps to Reproduce:
Notice for the code below to produce an exception, the resulting program has to be executed on a system that has not "seen" dbexpress yet - that means especially no registry entries for dbxdrivers.ini and dbxconnections.ini and also no dbx*.ini file in the application directory itself. If the files are present, the exception is not raised - although memory is corrupted elsewhere. Notice that in my tests the error  appears in this combination of SQLServer/MySQL, others may not produce the error exactly.

1. compile the attached console application but modify connection parameters to your username/password/server
2. deploy program to virgin system along with dbxmss30.dll and dbxmys30.dll
3. run program, following exception is thrown on the SQLServerQuery.SQL.Text assignment :
"Exception EAccessViolation in module MetaDataBug.exe at 00004390.
Access violation at address 00404390 in module 'MetaDataBug.exe'. Read of addres
s FFFFFFDD."

Reason: the exception is raised in SqlExpr.Pas, function GetIdOption in line
" if (Assigned(Connection) and (Connection.MetaData is TDBXDatabaseMetaDataEx)) then"
where code tries to access MetaData that is invalid.

Here is the test console app

program MetaDataBug;
{$APPTYPE CONSOLE}
uses
  Windows, Messages, SysUtils, Variants, Classes,
  WideStrings, FMTBcd, DB, SqlExpr;
var
  SQLServerConn, MySQLConn: TSQLConnection;
  SQLServerQuery, MySQLQuery: TSQLQuery;
  Count: Integer;
begin
  // 1st connection SQLServer
  SQLServerConn                := TSqlConnection.Create(nil);
  SQLServerConn.ConnectionName := 'MSSQLConnection';
  SQLServerConn.DriverName     := 'MSSQL';
  SQLServerConn.LibraryName    := 'dbxmss30.dll';
  SQLServerConn.VendorLib      := 'Oledb';
  SQLServerConn.GetDriverFunc  := 'getSQLDriverMSSQL';
  SQLServerConn.Params.Clear;
  SQLServerConn.Params.Add( 'HostName=mysqlserver' );
  SQLServerConn.Params.Add( 'Database=mysqldb' );
  SQLServerConn.Params.Add( 'USER_NAME=myuser' );
  SQLServerConn.Params.Add( 'PASSWORD=mypassword' );
  SQLServerQuery:= TSQLQuery.Create(nil);
  SQLServerQuery.SQLConnection:= SQLServerConn;

  // 2nd connection MySQL
  MySQLConn                := TSqlConnection.Create(nil);
  MySQLConn.ConnectionName := 'MySQLConnection';
  MySQLConn.DriverName     := 'MySQL';
  MySQLConn.LibraryName    := 'dbxmys30.dll';
  MySQLConn.VendorLib      := 'LIBMYSQL.dll';
  MySQLConn.GetDriverFunc  := 'getSQLDriverMYSQL';
  MySQLConn.Params.Clear;
  MySQLConn.Params.Add( 'HostName=127.0.0.1' );
  MySQLConn.Params.Add( 'Database=myMySQLdb' );
  MySQLConn.Params.Add( 'USER_NAME=root' );
  MySQLConn.Params.Add( 'PASSWORD=mypassword' );
  MySQLQuery:= TSQLQuery.Create(nil);
  MySQLQuery.SQLConnection:= MySQLConn;

  // now
  SQLServerConn.Connected:= true;
  SQLServerConn.Connected:= false; // this should invalidate SQLServerConn.SQLCOnnection.MetaData but it doesn't
  MySQLQuery.SQL.Text:= 'select count(*) as countorders from test.test'; // query any table
  MySQLQuery.Active:= true;
  Count:= MySQLQuery.FieldByName('countorders').AsInteger;
  if count=1000 then Inc(Count); // to suppress compiler warning

  // here we get an access violation for the SQLServer/mySQL combination
  SQLServerQuery.SQL.Text:= 'select count(*) as countorders from ARTIKEL'; // query any table
  WriteLn('Done.');
end.
Workarounds
None
Attachment
None
Comments

Markus Humm at 4/17/2008 4:03:59 AM -
Can you attach a sample project maybe?

Michal Trybulec at 5/30/2008 2:39:52 AM -
Well, don't have time to dig into SqlExpr, but the suggested workaround helped!

Using MySQL 5.0.32 with D2007 and Intraweb (9.0.39); all patches applied, Apr08 hotfix too. During tests we would get intermittent AVs when assigning a query to TSQLQuery.SQL.Text. Sometimes worked without problems, sometimes reported an error in one test or another; sometimes worked on one computer, barfed on another, stuff like that.

Unfortunately, I can't give you a project that would reproduce this reliably.

Brian Van Matre at 8/1/2008 8:23:04 AM -
I am hitting this problem.  I'd love to hear the 'suggested workaround'!  Thanks.

Brian Van Matre at 8/1/2008 8:30:21 AM -
More specifically, C++ Builder 2007 Version 11.0.2987.10779.

I have a TSQLQuery component that I use to do simple queries, called
GenericQuery.  I use it like this:

      vector<MyType*> retVal;
      GenericQuery->SQL->Clear();
      GenericQuery->SQL->Add("SELECT * FROM table_1 WHERE HIDDEN = 0");
      GenericQuery->Open();
      while (!GenericQuery->Eof)
      {
         TField* f1F = GenericQuery->Fields->FieldByName("field_1");
         TField* f2F = GenericQuery->Fields->FieldByName("field_2");
         TField* f3F = GenericQuery->Fields->FieldByName("field_3");
         TField* hidF = GenericQuery->Fields->FieldByName("hidden");
         retVal.push_back(new MyType(f1F,f2F,f3F,hidF));
         GenericQuery->Next();
      }
      GenericQuery->Close();
      return retVal;


If I call this method the first time, it's fine.  If I call it twice in a
row, it's fine.  If I call another TSQLQuery on the same connection after
calling this method, and then go back and call this method, I get an access
violation.  After the access violation, it works fine again.  The violation
occurs at the line 'GenericQuery->SQL->Clear();'.  The stack trace looks
like this:

:7c812a5b kernel32.RaiseException + 0x52
:008e3f0a ___raiseDebuggerException + 0x1A
:008ef598 ; ____ExceptionHandler
:008e32ed __ExceptionHandler + 0x1E
:7c90378b ntdll.RtlConvertUlongToLargeInteger + 0x46
:7c90eafa ntdll.KiUserExceptionDispatcher + 0xe
:006FA300 Sqlexpr::TSQLQuery::QueryChanged(Self=????, Sender=????)
:005EF680 Widestrings::TWideStringList::Changed(Self=????)
:004E22D2 TMyTypeOfForm::TMyTypeOfForm(this=:01416E20, Owner=:014C9650)

If I trace way down into the DBX code, the exception ends up being raised on
this line in SqlExpr.pas

  if (Assigned(Connection) and (Connection.MetaData is
TDBXDatabaseMetaDataEx)) then

If it matters I'm using an upscene driver and
Firebird 2.0 database.

Again, looking for the workaround to this problem.

Michal Trybulec at 9/24/2008 12:29:05 AM -
The fix is in the description:
In SqlExpr.pas, in TSQLConnection.DoDisconnect, set FMetaData to NIL
right after FreeAndNil(FDBXConnection);

Code to reproduce:

procedure TYIKDBTests.TestSqlExpr;
const
  CCount = 1000;
var
  k, LSize: integer;
  LConnection: TSQLConnection;
  LQuery: TSQLQuery;
  LMem: array of pointer;
begin
  LConnection := CreateSQLConnection;
  try
    LSize := LConnection.MetaData.InstanceSize;
    LQuery := TSQLQuery.Create(NIL);
    try
      LQuery.SQLConnection := LConnection;
      LQuery.SQL.Text := 'select * from TIMING;';
      LQuery.Open;
    finally
      FreeAndNIL(LQuery);
    end;

    SetLength(LMem, CCount);
    for k := 0 to CCount - 1 do
    begin
      GetMem(LMem[k], LSize);
      FillChar(LMem[k]^, LSize, $AA);
    end;
    for k := 0 to CCount - 1 do
      FreeMem(LMem[k]);

    LQuery := TSQLQuery.Create(NIL);
    try
      LQuery.SQLConnection := LConnection;
      LQuery.SQL.Text := 'select * from TIMING;';
      LQuery.Open;
    finally
      FreeAndNIL(LQuery);
    end;
  finally
    FreeAndNIL(LConnection);
  end;
end;

The GetMems in the middle are needed to overwrite memory that FMetaData is pointing to (and was freed when FDBXConnection was freed).

CreateSQLConnection is a function of the form:

function CreateSQLConnection: TSQLConnection;
begin
  Result := TSQLConnection.Create(NIL);                                        
  Result.DriverName := 'MySQL';                                                
  Result.GetDriverFunc := 'getSQLDriverMYSQL';                                  
  Result.LibraryName := 'dbxmys30.dll';                                        
  Result.VendorLib := 'LIBMYSQL.dll';                                          
  Result.Params.Values['HostName'] := CYIKDBServerIP;                          
  Result.Params.Values['Database'] := CYIKDBDatabase;                          
  Result.Params.Values['User_Name'] := CYIKDBUserName;                          
  Result.Params.Values['Password'] := CYIKDBPassword;                          
  Result.KeepConnection := FALSE;                                              
  Result.Connected := TRUE;                                                    
end;

Tomohiro Takahashi at 2/17/2010 12:27:20 AM -
Hi,
Today, we have encountered same issue with Delphi 2007...
I think a possible workaround is to set Connected property of TSQLConnection to True before manipulate TSQLQuery.SQL property.

> In SqlExpr.pas, in TSQLConnection.DoDisconnect, set FMetaData to NIL
right after FreeAndNil(FDBXConnection
AFAIK, this issue is already fixed in Delphi 2009.

Server Response from: ETNACODE01