2
votes

I'm working on a inno setup installer in which I need to execute multiple SQL-Management Studio scripts with the 'GO' Statement. My approach is to open the DB connection, loop through the files, splitting them into the GO-Blocks and execute each block. The first file runs through as expected. On the second file, when it comes to execution I get an access violation in method "ExecSQLMulti" at the line:

ADOCommand.ActiveConnection := DBConnection;

I think the issue is that the DBConnection object is still referenced by the previous ADOCommand object. But when I try this:

ADOCommand.ActiveConnection := null();

I get the error: Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another...

"Main" Method, iterates through the sql files:

function NKInstall_CreateDatabase: boolean;
var
  SQLScripts: TArrayOfString;
  ScriptTransactions: TStringList;
  i,ItemCount: integer;
  myDBConnection: Variant;
begin
  if not _PreconditionsOK_CreateDB() then begin
    Result := True;
    exit;
  end;

  if not ConnectToDatabase(SQLServerInstance.Text,myDBConnection) then exit;
  SQLScripts := CollectSQLScripts();
  ItemCount := GetArrayLength(SQLScripts)-1;
  ScriptTransactions := TStringList.Create();

  for I := 0 to ItemCount do begin
    SetProgressText('Datenbank wird erstellt...','Script: '+SQLScripts[i]);

    if LoadSQLScript(ExpandConstant('{app}\NKHLP\DB\'+SQLScripts[i]), ScriptTransactions) then begin
      if not ExecSQLMulti(myDBConnection,ScriptTransactions) then begin
        InstallSummary.Lines.Add('- DB Erstellung: Script '+SQLScripts[i]+' konnte nicht (komplett) ausgeführt werden.');
        //Result := False;   //*** Für Testphase des installers auskommentiert, damit Installation nicht abbricht.
        Result := True;
        gDBCreated := False;
        exit;
      end;
      ScriptTransactions.Clear();
    end
    else begin
      MsgBox('SQL Script '+ExpandConstant('{app}\NKHLP\DB\'+SQLScripts[i])+' konnte nicht eingelesen werden!',mbError,MB_OK);
      InstallSummary.Lines.Add('- DB Erstellung: Script '+SQLScripts[i]+' konnte nicht eingelesen werden.');
      //result := false;    //*** Für Testphase auskommentiert, damit Installation nicht abbricht.
      result := True;
      gDBCreated := False;
      exit;
    end;
    //Log('File done...'); //Debug
  end;
  gDBCreated := True;
  Result := True;
end;

Establishes connection to DB:

function ConnectToDatabase(DataSourceName: string; var Connection: Variant): boolean;
begin
log('Connecting...');
  try
    Connection := CreateOleObject('ADODB.Connection');
    Connection.CursorLocation := 3;
    Connection.ConnectionString := 'Provider=SQLOLEDB;' +                   // provider
                                   'Data Source=' + DataSourceName +';' +   // server name
                                   'Initial Catalog=master;' +              // default database
                                   'Integrated Security=SSPI;';             // Use Windows Authentication
    Connection.Open;
    Result := True;
  except
    Connection := Null();
    MsgBox('Die Verbindung zur Instanz "'+DataSourceName+'" konnte nicht hergestellt werden: '+#10#13+#10#13+GetExceptionMessage,mbError,MB_OK);
    Result := False;
  end
end;

Executes all the GO-Blocks:

function ExecSQLMulti(var DBConnection: Variant; Transactions: TStringList): boolean;
var
  ADOCommand: Variant;
  i: integer;
  CurrentQuery: string;
begin
  if Transactions.Count = 0 then begin
    Result := True;
    exit;
  end;
  try
    ADOCommand := CreateOleObject('ADODB.Command');
    ADOCommand.CommandType := adCmdText;
    ADOCommand.ActiveConnection := DBConnection;

    for i := 0 to Transactions.Count-1 do begin
      CurrentQuery := Transactions[i];
      if Length(CurrentQuery) > 0 then begin
        ADOCommand.CommandText := CurrentQuery;
        ADOCommand.Execute(NULL,NULL,adExecuteNoRecords);
      end;
    end;
    result := True;
  except
    MsgBox('Fehler beim ausführen eines SQL Befehls: '+GetExceptionMessage()+#10#13#10#13+
           'Query: '+CurrentQuery,
           mbError,
           MB_OK);
  end;
end;
2
Did you resolve this issue? I'm facing the same problem today. - k3flo
Actually I can't remember what the exact problem was. If I remember right the problem was, how I created and passed the connection object. I will post the refactored version. - Marco Rebsamen

2 Answers

1
votes

It seems like my approach of how I created and passed the connection object was wrong or just not working. I've refactored it to this:

function RunSQLScript(ScriptPath: String; SQLInstance: String; DBName: string; CommandTimeout: integer): boolean;
var
  ScriptTransactions: TStringList;
  ADOCommand: Variant;
  myDBConnection: Variant;
begin
  ScriptTransactions := TStringList.Create();

  if not ConnectToDatabase(SQLInstance, DBName, myDBConnection) then exit;

  ADOCommand := CreateOleObject('ADODB.Command');
  ADOCommand.ActiveConnection := myDBConnection;
  ADOCommand.CommandTimeout := CommandTimeout;
  ADOCommand.CommandType := adCmdText;

  if LoadSQLScript(ScriptPath, ScriptTransactions) then begin
    log('Executing Transactions...');
    if not ExecMultiTransaction(ADOCommand, ScriptTransactions) then begin
      MsgBox('SQL Script '+ScriptPath+' konnte nicht (komplett) ausgeführt werden!',mbError,MB_OK);
      Result := False;
    end
    else begin
      log('Sucessful');
      Result := True;
    end;
  end
  else begin
    MsgBox('SQL Script '+ScriptPath+' konnte nicht eingelesen werden!',mbError,MB_OK);
    result := False;
  end;
end;

function ExecMultiTransaction(var ADOCommand: Variant; Transactions: TStringList): boolean;
var
  i: integer;
  CurrentQuery: string;
begin
  if Transactions.Count = 0 then begin
    Result := True;
    exit;
  end;
  try
    for i := 0 to Transactions.Count-1 do begin
      CurrentQuery := Transactions[i];
      if Length(CurrentQuery) > 0 then begin
        ADOCommand.CommandText := CurrentQuery;
        ADOCommand.Execute(NULL,NULL,adExecuteNoRecords);
      end;
    end;
    result := True;
  except
    MsgBox('Fehler beim ausführen eines SQL Befehls: '+GetExceptionMessage()+#13#10#13#10+
           'Query: '+CurrentQuery,
           mbError,
           MB_OK);
  end;
end;

In the RunSQLScript function I now create the connection and pass only the ADOCommand object to the ExecMultiTransaction function where I change the ADOCommand.CommandText property and execute the command. This way I have one connection per script.

0
votes

Perhaps it will be useful. I also got an error "access violation" when I create multiple connections. I wrote several functions for working with SQL database in main script (may be functions are not optimal, but they satisfy my goals):

procedure LogPrint(TlogFile, TlogText: String);
begin
  SaveStringToFile(TlogFile, GetDateTimeString('dd/mm/yyyy hh:nn:ss', '.', ':') + ' ' + TlogText + #13#10, True);
end;


//Function for write ADO errors to log
function ADOErrorPrintFunc(ADOConnection: Variant; FuncNameForLog, logFile: String): Boolean;
var
  ADOError: Variant; 
  I : Integer;  
begin
  if VarIsEmpty(ADOConnection) or (ADOConnection.Errors.Count = 0) then begin
    LogPrint(logFile, FuncNameForLog + ': ' + GetExceptionMessage);
    Result := False;
  end else begin        
    for I := 0 to ADOConnection.Errors.Count - 1 do
      begin
        ADOError := ADOConnection.Errors.Item(I);
        LogPrint(logFile, FuncNameForLog + ' (error) err#: ' + IntToStr(ADOError.Number) + '; ' + 'Source: ' + ADOError.Source + '; ' + 'Description: ' + ADOError.Description);
      end;
    Result := True;
  end;
end;


//Function for create ADODB.Connection once
//Example: SQLobj := _SQL_CreateADOConnection(logFile);
function _SQL_CreateADOConnection(logFile: String): Variant;
var
  ADOConnection: Variant;  
begin
  try    
    ADOConnection := CreateOleObject('ADODB.Connection'); //create the ADO connection object     
    LogPrint(logFile, '_SQL_CreateADOConnection (success)');
  except
    ADOErrorPrintFunc(ADOConnection, '_SQL_CreateADOConnection', logFile);
  end;
  Result := ADOConnection;
end;


//Function for open connection. Must used after _SQL_CreateADOConnection
//For example, ConnectionString for connect to SQL database: 'DRIVER=SQL Server;SERVER=localhost\SQLEXPRESS;DATABASE=master;UID=login;PWD=password;'      
//ConnectionString for connect to Interbase database:  'DRIVER={Easysoft IB6 ODBC};SERVER=localhost;DATABASE=localhost:c:\PathToDb\DBName.gdb;UID=login;PWD=password;'      
//Example: SQLobj := _SQL_OpenConnection(ADOConnection, ConnectionString, logFile);
function _SQL_OpenConnection(ADOConnection: Variant; ConnectionString, logFile: String): Boolean; 
begin
  try
    ADOConnection.ConnectionString := ConnectionString;   
    ADOConnection.Open;           
    LogPrint(logFile, '_SQL_OpenConnection (success)');
    Result := True;
  except
    ADOErrorPrintFunc(ADOConnection, '_SQL_OpenConnection', logFile);
    ADOConnection := Null();
    Result := False;
  end;
end;


//Function for create ADODB.Command object with activate previously opened connection. Use it after function _SQL_OpenConnection return True value.
//Example: SQLobjCmd := _SQL_CreateADOCommand(ADOConnection, logFile);
function _SQL_CreateADOCommand(ADOConnection: Variant; logFile: String): Variant;
var
  ADOCommand: Variant;  
begin
  try    
    ADOCommand := CreateOleObject('ADODB.Command');
    ADOCommand.ActiveConnection := ADOConnection;   
    LogPrint(logFile, '_SQL_CreateADOCommand (success)');
  except
    ADOErrorPrintFunc(ADOConnection, '_SQL_CreateADOCommand', logFile);
  end;
  Result := ADOCommand;
end;


//Function for use "select" query. Reads from the database values from the table and returns an array of column "ResultField". 
//SQLresArr[0] from example - number of elements. If SQLresArr[0] = '' or SQLresArr[0] = '-1' this means error in your SQL query.
//Example: SQLresArr := _SQL_Select(ADOConnection, ADOCommand, 'select * from RDB$ROLES', 'RDB$ROLE_NAME', logFile);
function _SQL_Select(ADOConnection, ADOCommand: Variant; SQLQuery: AnsiString; ResultField, logFile: String): TArrayOfString;
var
  ADORecordset: Variant;  
  tmpArray : TArrayOfString;
  i : Integer;  
begin
  SetArrayLength(tmpArray, 1);
  tmpArray[0] := '';
  try         
    ADOCommand.CommandText := SQLQuery;        
    ADOCommand.Execute(NULL, NULL, adCmdText or adExecuteNoRecords);         
    ADOCommand.CommandType := adCmdText;        
    ADORecordset := ADOCommand.Execute;      
    if ADORecordset.EOF then begin       
      tmpArray[0] := '0';
    end else begin
      i := 0;
      while not ADORecordset.eof do begin
        i := i + 1;
        tmpArray[0] := IntToStr(i);
        SetArrayLength(tmpArray, i + 1);
        tmpArray[i] := ADORecordset.Fields(ResultField).Value;
        ADORecordset.MoveNext;
      end;
    end;
    LogPrint(logFile, '_SQL_Select (success): ' + SQLQuery);
  except
    ADOErrorPrintFunc(ADOConnection, '_SQL_Select', logFile);
    tmpArray[0] := '-1';
  end;
  Result := tmpArray;
end;


//Function for execute SQL query like "update", "alter", etc
//Example: _SQL_Query(ADOConnection, ADOCommand, 'update TABLE set FIELD1 = 1 where FIELD2 = 2', logFile)
function _SQL_Query(ADOConnection, ADOCommand: Variant; SQLQuery, logFile: String): Boolean;
var
  ADORecordset: Variant;  
begin
  try
    ADOCommand.CommandText := SQLQuery;    
    ADOCommand.Execute(NULL, NULL, adCmdText or adExecuteNoRecords);
    ADOCommand.CommandType := adCmdText;
    ADORecordset := ADOCommand.Execute;               
    LogPrint(logFile, '_SQL_Query (success): ' + SQLQuery);
    Result := True;
  except
    LogPrint(logFile, '_SQL_Query(' + SQLQuery + '): ' + GetExceptionMessage);    
    Result := False;
  end;
end;



//Function for close connection. 
//Example: _SQL_Disconnect(ADOConnection, logFile);
function _SQL_Disconnect(ADOConnection: Variant; logFile: String): Boolean;  
begin
  Result := True;
  try
    ADOConnection.Close;
    LogPrint(logFile, '_SQL_Disconnect (success)');
  except
    ADOErrorPrintFunc(ADOConnection, '_SQL_Disconnect', logFile);
    Result := False;
  end;
end;

Example for Interbase database:

[CODE]
var
  I : Integer;
  logFile: string; 
  IBDriver, IBServer, IBDatabase, IBLogin, IBPassword, IBPasswordReserved, 
  ADO_IB_Obj_ConnectionString: string;
  ADO_IB_Obj_Connected: Boolean;  
  SQLresArr: TArrayOfString;
  ADO_IB_Obj, ADO_IB_ObjCmd: Variant;
begin
  IBDriver := '{Easysoft IB6 ODBC}'
  IBServer := 'localhost'
  IBDatabase := 'C:\Path\DbName.gdb'
  IBLogin := 'SYSDBA';
  IBPassword := 'masterkey';
  IBPasswordReserved := 'tttttttt';
  logFile := 'C:\SQLlog.txt';

  ADO_IB_Obj_Connected := False;
  ADO_IB_Obj := _SQL_CreateADOConnection(logFile);

  // try connect to database with standard password
  ADO_IB_Obj_ConnectionString := 'DRIVER=' + IBDriver + ';SERVER=' + IBServer + ';DATABASE=' + IBServer + ':' + IBDatabase + ';UID=' + IBLogin + ';PWD=' + IBPassword + ';'
  if _SQL_OpenConnection(ADO_IB_Obj, ADO_IB_Obj_ConnectionString, logFile) then begin
    ADO_IB_Obj_Connected := True;
  end else begin
    //try connect to database with other password
    ADO_IB_Obj_ConnectionString := 'DRIVER=' + IBDriver + ';SERVER=' + IBServer + ';DATABASE=' + IBServer + ':' + IBDatabase + ';UID=' + IBLogin + ';PWD=' + IBPasswordReserved + ';'
    if _SQL_OpenConnection(ADO_IB_Obj, ADO_IB_Obj_ConnectionString, logFile)  then begin
      ADO_IB_Obj_Connected := True;
    end; 
  end; 

  //working with database only if connection is opened successfully
  if ADO_IB_Obj_Connected then begin
    ADO_IB_ObjCmd :=_SQL_CreateADOCommand(ADO_IB_Obj, logFile); //create once
    _SQL_Query(ADO_IB_Obj, ADO_IB_ObjCmd, 'update TABLE1 set FIELD1 = 1 where FIELD2 = 2', logFile);
    SQLresArr := _SQL_Select(ADO_IB_Obj, ADO_IB_ObjCmd, 'select RDB$RELATION_NAME from RDB$RELATIONS', 'RDB$RELATION_NAME', logFile);
    For i := 1 to GetArrayLength(SQLresArr) - 1 do begin
      LogPrint(logFile, 'Table ' + IntToStr(i) + ' = "' + Trim(SQLresArr[i]) + '"; Tables count = ' + SQLresArr[0]);
    end;

    _SQL_Disconnect(ADO_IB_Obj, logFile);
  end;
end;