Delphi: Database is locked (SQLite)

Alvin Lin picture Alvin Lin · Jul 31, 2014 · Viewed 9k times · Source

I am facing two problems...

(1) When I try to write to a Database (SQLite) by using Delphi XE6, I always get the Database is locked error message. I am certain that I close the database everytime I access it by using the command FDConnection1.Close;

(2) How do I INSERT INTO a table from incoming parameters? I have the following incoming parameters

procedure TStock_Bookkeeping.Write_To_DB(const Stock_Code, Stock_Name,
Tran_Date, Buy_Sell, Price_Per_Share, Num_Shares, Trans_Fee: string);

and tried to write into the table with the following SQL command:

sSQL := 'INSERT INTO Each_Stock_Owned(Stock_Code, Stock_Name, Tran_Date, Buy_Sell,
         Price_Per_Share, Num_Shares, Trans_Fee) 
         VALUES (Stock_Code, Stock_Name, Tran_Date, Buy_Sell, Price_Per_Share,  
         Num_Shares, Trans_Fee)';

but it does not seem to work...

The following is the complete procedure I am having trouble with

procedure TStock_Bookkeeping.Write_To_DB(const Stock_Code, Stock_Name,
  Tran_Date, Buy_Sell, Price_Per_Share, Num_Shares, Trans_Fee: string);
var
  query : TFDQuery;
  sSQL: string;
begin
    query := TFDQuery.Create(nil);
  try
    ConnectToSQLite;
    query.Connection := FDConnection1;
  if Stock_Code.IsEmpty then
    ShowMessage('Stock Code Cannot Be Empty')
    else
      if Stock_Name.IsEmpty then
        ShowMessage('Stock Name Cannot Be Empty')
        else
          if Tran_Date.IsEmpty then
            ShowMessage('Transaction Date Cannot Be Empty')
            else
            begin
//              sSQL := 'INSERT INTO Each_Stock_Owned(Stock_Code, Stock_Name, Tran_Date, Buy_Sell, Price_Per_Share, Num_Shares, Trans_Fee) VALUES (Stock_Code, Stock_Name, Tran_Date, Buy_Sell, Price_Per_Share, Num_Shares, Trans_Fee)';
              sSQL := 'INSERT INTO Each_Stock_Owned(Stock_Code, Stock_Name, Tran_Date, Buy_Sell, Price_Per_Share, Num_Shares, Trans_Fee) VALUES (1,2,3,4,5,6,7)';
              query.sql.Text := sSQL;
              query.ExecSQL;
              query.Open();
        end;
  finally
    query.Close;
    query.DisposeOf;
    DisconnectFromSQLite;
  end;

end;

Any hints will be very appreciated. Thanks in advance.

Answer

Frazz picture Frazz · Jul 31, 2014

There are two techniques to executing a dynamic SQL statement. But I'll use a shorter SQL, to concentrate on the logic:

The pure way (using parameters)

q.SQL.Text:=
  'INSERT INTO Each_Stock_Owned (Stock_Code, Stock_Name) '+
  'VALUES (:Stock_Code, :Stock_Name)';
q.Prepare; //Optional
q.ParamsByName('Stock_Code').AsString := Stock_Code;
q.ParamsByName('Stock_Name').AsString := Stock_Name;
q.ExecSQL;

The dirty way (building SQL)

q.SQL.Text:=
  'INSERT INTO Each_Stock_Owned (Stock_Code, Stock_Name) VALUES ('+
  QuotedStr(Stock_Code) + ', '+
  QuotedStr(Stock_Name) + ')';
q.ExecSQL;

The differences are significant. The dirty way exposes you to SQL injection problems (as in most other languages, when you build SQL dinamically but without parameters). This could be or not be a problem for you. If you know that the procedure is only called privately by your own code, and that those procedure parameter values can only contain good values... or if you do some good parameter checking before building and executing your SQL... then you are safe.

But if you do it with parameters (the pure way) you are automatically protected from SQL injection, as the SQL statement is validated by the engine, without knowing the parameter values. So the SQL statement structure is known by the engine and cannot be altered by the actual values.

Another consideration is how frequently you will execute that INSERT statement. The pure way allows you to prepare the query ONCE, and execute it MANY TIMES with different parameter values (you must not destroy the query object, nor change the SQL property, and you must call the Prepare method once). If you run it frequently within a loop then it can be more efficient than building the SQL many times the dirty way. OTOH if you just need to insert one single row, it may pose a bit more overhead.

=================

As an aside... CL is right... those values should not be strings. Keep in mind that the Parameter object has many properties to handle different data types:

  q.ParamsByName('somedate').AsDateTime := Date;
  q.ParamsByName('somenumeric').AsFloat := 3/4;

... and so on.

If you don't use parameters, then things get difficult. The QuoteStr function is good for strings, but if you want to burn dates and currencies and other value types directly in your SQL you have to know what you are doing. You may encounter many different problems... locale specific or format settings that are not good for comunicating with your server, which may be on the opposite end of the world, or may just not be able to read values formatted that way. You may have to handle engine specific formatting and conversion problems.

If you do use parameters, then FireDAC should take care of all this for you ;)