Delphi xe5 (FireMonkey) - Writing too slow to SQLite - sqlite

I'm making an cross-platform application that should write values to a SQLite database. Right now it writes really slow and it freezes after a while.
I'm calling this code each 10ms:
SQLQueryInsert.SQL.Text := 'insert into task (id, latitude) values (:id, :lat)';
SQLQueryInsert.Prepared := true;
SQLQueryInsert.ParamByName('id').AsInteger := 1;
SQLQueryInsert.ParamByName('latitude').AsFloat := 12332145;
SQLQueryInsert.ExecSQL();
Any suggestions?

before you start, do this (create the vars ParamId and ParamLat in a suitable location)
SQLQueryInsert.SQL.Text := 'insert into task (id, latitude) values (:id, :lat)';
SQLQueryInsert.Prepared := true;
ParamId := SQLQueryInsert.ParamByName('id');
ParamLat := SQLQueryInsert.ParamByName('latitude');
In the code you execute everytime:
ParamId.AsInteger := 1;
ParamLat.AsFloat := 12332145;
SQLQueryInsert.ExecSQL();

Related

How to get the ID of the last record inserted in SQLite with Delphi 10?

Delphi 10 with Firemonkey and SQLite: After running the code below I want to get the ID of the last record inserted into an SQLite table. How do I get the last ID?
NOTE: The ID field of Table 1 is autoincrement.
var myQr: TFDQuery;
begin
myQr := TFDQuery.Create(Self);
with myQr do begin
SQL.Add('Insert into table1 values (:_id, :_name, :_dthr)');
Params.ParamByName('_id').ParamType := TParamType.ptInput;
Params.ParamByName('_id').DataType := TFieldType.ftInteger;
Params.ParamByName('_id').Value := null;
ParamByName('_name').AsString := 'name test';
ParamByName('_dthr').AsDateTime := Now;
ExecSQL;
end;
// How to get last ID? <<<<<<<<<<<<<=================
myQr.DisposeOf;
You could query last_insert_rowid if your ID column is declared as INTEGER PRIMARY KEY. In such case the column becomes alias for the ROWID. If that is your case, you can query it natively e.g. this way:
uses
FireDAC.Phys.SQLiteWrapper;
function GetLastInsertRowID(Connection: TFDConnection): Int64;
begin
Result := Int64((TObject(Connection.CliObj) as TSQLiteDatabase).LastInsertRowid);
end;
Or in common way by calling GetLastAutoGenValue method:
function GetLastInsertRowID(Connection: TFDConnection): Int64;
begin
Result := Int64(Connection.GetLastAutoGenValue(''));
end;

Table locks in SQLite accessed by fireDAC

I'm working on porting a set of paradox tables to SQLite. In order to do so, I created a test application that simulates (somewhat) the current usage scenario: multiple users accessing the same DB file and performing simultaneous read and writes.
The application is very simple: it will start several threads that each create a connection, opens a table and will randomly read, update or insert inside the table.
Almost immediately, The application encounters a "database table locked" error. I have tried several things to attempt to work around it but nothing seems to work. What am I doing wrong ?
Here is the code internal to the threads:
procedure testDB(TargetFolder: string);
var
Conn: TFDConnection;
Table: TFDTable;
i: Integer;
begin
randomize;
Conn := TFDConnection.Create(nil);
try
Conn.DriverName := 'SQLite';
Conn.LoginPrompt := false;
Conn.Params.clear;
Conn.Params.Database := TPath.Combine(TargetFolder, 'testDB.sdb');
Conn.Params.Add('DriverID=SQLite');
// all this is the result of several attemp to fix the table locking error. none worked
Conn.Params.Add('LockingMode=Normal');
Conn.Params.Add('Synchronous=Normal');
Conn.UpdateOptions.UpdateMode := TUpdateMode.upWhereAll;
Conn.UpdateOptions.LockWait := True;
Conn.UpdateOptions.LockMode := TFDLockMode.lmPessimistic;
Conn.UpdateOptions.LockPoint := TFDLockPoint.lpImmediate;
Conn.UpdateOptions.AssignedValues := [uvLockMode,uvLockPoint,uvLockWait];
Conn.Open();
Conn.ExecSQL('CREATE TABLE IF NOT EXISTS ''test'' (''ID'' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,''data1'' TEXT NOT NULL,''data2'' INTEGER NOT NULL)');
Table := TFDTable.Create(nil);
try
table.Connection := Conn;
while True do
begin
case Trunc(Random(10)) of
0..3:
begin
table.Open('test');
try
if table.Locate('data1', 'name'+intToStr(Trunc(Random(10))),[TLocateOption.loCaseInsensitive]) then
begin
table.Edit;
table.FieldByName('data2').AsInteger := table.FieldByName('data2').AsInteger + 1;
table.Post;
end;
finally
table.close;
end;
end;
4..8:
begin
table.Open('test');
try
i := Trunc(Random(10));
if not table.Locate('data1', 'name'+ i.ToString,[TLocateOption.loCaseInsensitive]) then
begin
table.AppendRecord([null, 'name'+ i.ToString, 0]);
end;
finally
table.close;
end;
end
else
break;
end;
end;
finally
FreeAndNil(Table);
end;
finally
FreeAndNil(Conn);
end;
end;
Thanks to Victoria, I managed to find the right parameters.
Conn := TFDConnection.Create(nil);
try
Conn.DriverName := 'SQLite';
Conn.LoginPrompt := false;
Conn.Params.clear;
Conn.Params.Database := TPath.Combine(TargetFolder, 'testDB.sdb');
Conn.Params.Add('DriverID=SQLite');
Conn.Params.Add('SharedCache=False');
Conn.Params.Add('LockingMode=Normal');
Conn.Params.Add('Synchronous=Normal');
Conn.UpdateOptions.LockWait := True;
Conn.Open();
Thanks again

SQL database data into an array

I currently have a program which connects to a database and displays the data on a DBGrid, however I need to extract this data so I can use it in another algorithm.
When I use the command: select scores from quiz
It shows the values on the screen. (23,55,64)
How do I get these values into an array so that
[0]=23
[1]=55
[2]=64
Thanks in advance.
Best to use TList (which is an array wrapper object). Below is an excerpt from some code that I use. TSomeRect is a record to store the field data of each row in.
function CreateQuery(pConnection: Tsqlconnection; pTransaction: TSQLTransaction): TSQLQuery;
begin
result := TSQLQuery.Create(nil);
result.Database := pConnection;
result.Transaction := pTransaction
end;
var
connect: TSQLite3Connection;
SQLQuery1: TSQLQuery;
transact: TSQLTransaction;
Query : TSQLQuery;
lst :TList<TSomeRect>;
rec :TSomeRect;
begin
lst :=TList<TSomeRect>.create;
connect:=TSQLite3Connection.create(nil);
connect.LoginPrompt := False;
connect.DatabaseName := 'c:\path\to\database.sqlite';
connect.KeepConnection := False;
transact:=TSQLTransaction.create(nil);
transact.action:=caNone;
transact.database:=connect;
connect.Transaction:=transact;
Query := CreateQuery(Connect, Transact);
Query.SQL.Text := 'select * from table';
Connect.Open;
Query.Open;
while not Query.Eof do
begin
rec.field1:= Query.FieldByName('field1').AsInteger;
rec.field2:= Query.FieldByName('field2').Asstring;
lst.add(rec);
Query.Next;
end;
Query.Close;
Connect.Close;
Query.Free;
Transact.Free;
Connect.Free;

Error using SQLite ATTACH - DETACH in Delphi

I experience a, for me unsolvable, problem with ATTACH and DETACH in SQLite, using Delphi (Firedac).
I have one database file connected and attach a second one with:
FDConnection1.ExecSQL('ATTACH DATABASE "' + Import_DB_filename + '" AS IMPORTDB;');
Therein, the variable 'Import_DB_filename' contains the full path and filename of the database file.
This works OK and I can access both databases within the connection through the FireDac queries, and can do my coding without problems.
However, things go wrong upon detaching:
FDConnection1.ExecSQL('DETACH DATABASE IMPORTDB;');
In debugging mode, I always get the error:
Debugger Exception Notification
E Project My_Program.EXE raised exception class $C0000005 with message 'access violation at 0x00405d7b: read of address 0x00000000'.
Apparently something goes wrong with the memory assignments, since the debugger stops in a (assembly) function SysFreeMem(P:Pointer): Integer; in GETMEM.INC.
Whatever I try, the error persists and associates a memory leak that eventually leads to a crash of the compiler (Delphi Seattle Enterprise).
Even attaching and subsequent detaching of the database without passing any code results in the same error.
(FDconnection: locking mode = lmNormal;
JournalMode = jmOff or jmWALL or jmdelete)
I do hope that you can help me out on this lasting problem.
If you run the project below, you should find that:
You can access two Sqlite databases quite happily using separate FDConnections and FDTables.
You can move data from a table in one db to a table of the same name in the other using a FireDAC FDDataMove component.
Code:
unit BatchMoveu;
interface
[...]
type
TForm3 = class(TForm)
FDConnection1: TFDConnection;
DBGrid1: TDBGrid;
DataSource1: TDataSource;
DBNavigator1: TDBNavigator;
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
FDPhysSQLiteDriverLink1: TFDPhysSQLiteDriverLink;
Button1: TButton;
FDTable1: TFDTable;
FDConnection2: TFDConnection;
DataSource2: TDataSource;
DBGrid2: TDBGrid;
btnBatchMove: TButton;
FDDataMove1: TFDDataMove;
FDTable2: TFDTable;
procedure btnBatchMoveClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
procedure PopulateTable1;
procedure TestDataMove;
public
procedure CreateDatabase(DBName : String; FDConnection : TFDConnection;
FDTable : TFDTable);
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
const
DBName1 = 'd:\delphi\code\sqlite\db1.sqlite';
DBName2 = 'd:\delphi\code\sqlite\db2.sqlite';
procedure TForm3.Button1Click(Sender: TObject);
begin
CreateDatabase(DBName1, FDConnection1, FDTable1);
CreateDatabase(DBName2, FDConnection2, FDTable2);
PopulateTable1;
FDTable2.Open;
end;
procedure TForm3.CreateDatabase(DBName : String; FDConnection : TFDConnection;
FDTable : TFDTable);
var
AField : TField;
i : Integer;
begin
if FileExists(DBName) then
DeleteFile(DBName);
AField := TLargeIntField.Create(Self);
AField.FieldName := 'ID';
AField.DataSet := FDTable;
AField.Name := AField.DataSet.Name + 'IDField';
AField := TWideStringField.Create(Self);
AField.Size := 80;
AField.FieldName := 'Name';
AField.DataSet := FDTable;
AField.Name := AField.DataSet.Name + 'NameField';
FDConnection.Params.Values['database'] := DBName;
FDConnection.Connected:= True;
FDTable.CreateTable(False, [tpTable]);
end;
procedure TForm3.PopulateTable1;
var
i : Integer;
begin
FDTable1.Open;
for i:= 1 to 1000 do begin
FDTable1.InsertRecord([i, 'Row ' + IntToStr(i)]);
end;
FDTable1.Close;
//FDConnection1.Commit;
FDTable1.Open;
end;
procedure TForm3.TestDataMove;
var
Item : TFdMappingItem;
begin
Item := FDDataMove1.Mappings.Add;
Item.SourceFieldName := 'ID';
Item.DestinationFieldName := 'ID';
Item := FDDataMove1.Mappings.Add;
Item.SourceFieldName := 'Name';
Item.DestinationFieldName := 'Name';
FDDataMove1.Source := FDTable1;
FDDataMove1.Destination := FDTable2;
FDDataMove1.Options := FDDataMove1.Options - [poOptimiseSrc];
FDDataMove1.Execute;
FDConnection2.Connected := False;
FDTable2.Open;
end;
procedure TForm3.btnBatchMoveClick(Sender: TObject);
begin
TestDataMove;
end;
procedure TForm3.FormDestroy(Sender: TObject);
begin
FDConnection1.Close;
end;
end.

Pascal changing recursion to while loop with pointer

Hi I'm learning pascal and testing some functions
I made a recursive insert procedure like this.
if node exist compare two keys, and if not, make a room for new node.
procedure INSERT (KEY : integer; var NODE : NODEPTR);
begin
if NODE = Nil then
begin
New (NODE);
NODE^.KEY := KEY;
NODE^.LEFT := Nil;
NODE^.RIGHT := Nil
end
else
if KEY < NODE^.KEY then
INSERT (KEY, NODE^.LEFT)
else
INSERT (KEY, NODE^.RIGHT)
end;
and What I'm trying to do is changing recursive function to while-loop.
so I made procedure like this
if node exist do while loop until node is empty.
and after while loop is over, make a new node
procedure INSERT (KEY : integer; var NODE : NODEPTR);
begin
while NODE <> nil do
begin
if KEY < NODE^.KEY then
NODE:=NODE^.LEFT
else
NODE:=NODE^.RIGHT
end;
New (NODE);
NODE^.KEY := KEY;
NODE^.LEFT := Nil;
NODE^.RIGHT := Nil
end;
when first node is root, while loop is true and execute this code
but after this, while loop changes to false and make a new node.
if KEY < NODE^.KEY then
NODE:=NODE^.LEFT
else
NODE:=NODE^.RIGHT
Eventually there is no node connection, and this program just keep making new node.
Is there anything that i missed? or any improvising about the second code?
thanks in advance :)
The thing you missed is that you never link up the nodes (and in the current setup, you can't actually linkup the nodes). You have fields on your record for the left and right node of a node, but you don't assign them. So you end up just creating nodes without every linking them up; you have a linked list with the links missing.
At one point, you'll need something along the lines of NODE^.RIGHT := NEWNODE (or NODE^.LEFT := NEWNODE, of course).
You'll need something along the ways of (my Pascal is a bit rusty, so beware of syntax errors ;) ):
procedure INSERT(key: Integer; var NODE: NODEPTR)
begin
NODEPTR root := NODE;
if (key < root^.KEY) then begin
while (root^.LEFT <> nil) do begin
root := root^.LEFT;
end;
new(NODE);
NODE^.KEY := key;
root^.LEFT := NODE;
node^.RIGHT := root;
end else begin
while (root^.Right <> nil) do begin
root^ := root^.RIGHT;
end;
new(NODE);
NODE^.KEY := key;
root^.RIGHT := NODE;
NODE^.LEFT := root;
end;
end;
There's lots of improvements and beautifications to be made in the example above, but I think it shows the general idea.

Resources