Why doesn't my SQLite database add data until I close my application? - sqlite

My Delphi application is using FireDac and an SQLite database. I've noticed that updates are being saved in a journal file and the database file is not actually updated until I close my application.
The application is making lots of 'batch updates' to the database. Each individual update is inside a TFDQuery.StartTransaction ... TFDQuery.Commit pair. Despite this, it seems all updates are held in the journal file until the application ends.
How can I force SQLite to update the database after each batch of updates rather than when my application finishes?
I've tried changing the SQLite db to WAL but the same thing happens.
Despite using 'StartTransaction' and 'Commit' the data stays in the journal until the application ends.
try
Query.Connection := FDConnection1;
FDConnection1.Open;
FDConnection1.StartTransaction;
Query.SQL.Text := 'select 1 from t_Manufacturers where m_Name = ' + QuotedStr(ManString);
Query.Open;
if Query.RecordCount = 0 then begin
{ not found, so add }
Query.SQL.Text := 'insert into t_Manufacturers (m_Name, m_ManUID) values (:Name, null)';
Query.ParamByName('Name').AsString := ManString;
Query.ExecSQL;
{ save m_ManUID for logging }
Query.SQL.Text := 'select m_ManUID from t_Manufacturers where m_Name = ' + QuotedStr(ManString);
Query.Open;
end;
Result := Query.FieldByName(m_ManUID).AsInteger;
FDConnection1.Commit;
except
on E : EDatabaseError do begin
MessageDlg('Database error adding manufacturer: ' + E.Message, mtError, [mbOk], 0);
FDConnection1.Rollback;
end;
No error messages or issues. Providing the application finishes OK, the database is updated as expected, so I'm happy that my programming and SQL is doing exactly what I need in that respect.

It is very dubious that "it seems all updates are held in the journal file until the application ends". SQLite3 is very serious about writing data - more serious than most DB engines I know. Just check https://www.sqlite.org/atomiccommit.html
I suspect you are somewhat confused by the presence of the journal file. After a transaction, the journal file is still kept there on disk, ready for any new write operation. But the data is actually written in the main file.
Just write some data, then kill the application before closing it (using the task manager). Then re-open the file (re-start the app): I am almost sure you will see the data properly stored.
FireDAC is "cheating" with the default journalization mode, for best performance. It uses some default values which may be confusing. As stated by FireDAC documentation: Set LockingMode to Normal to enable shared DB access. Set Synchronous to Normal or Full to make committed data visible to others.

You are using FDConnection1.StartTransaction; In this state condition, the transaction is still on the memory (cache) without any end. Therefore, you need to end your transaction with commit command such like FDConnection1.Commit;

OK, I went back to basics and wrote a test application with all the database activity confined to a single procedure fired by a button click. In that procedure I added multiple rows to a table using a for loop.
The for loop is surrounded by StartTransaction and Commit calls. Running through the code in the debugger, the journal file is created on the first call to ExecSQL. However, the file remains there after the loop has completed and Commit has been called.
The database is only updated and the journal file deleted when Close is called at the end of the procedure.
procedure TForm1.Button1Click(Sender: TObject);
var
Query : TFDQuery;
Index : Integer;
begin
FDConnection1.DriverName := 'SQLite';
FDConnection1.Params.Values['Database'] := 'C:\Testing\test.db';
FDConnection1.Open;
Query := TFDQuery.Create(nil);
Query.Connection := FDConnection1;
try
FDConnection1.StartTransaction;
for Index := 1 to 10 do begin
Query.SQL.Text := 'insert into Table1 (Name, IDNum) values (:Name, :IDNum)';
Query.ParamByName('Name').AsString := 'Test_Manufacturer_' + IntToStr(Index);
Query.ParamByName('IDNum').AsInteger := Index;
Query.ExecSQL;
end;
FDConnection1.Commit;
except
on E : EDatabaseError do begin
MessageDlg('Database error adding manufacturer: ' + E.Message, mtError, [mbOk], 0);
FDConnection1.Rollback;
end;
end;
Query.Destroy;
FDConnection1.Close;
end;
I'm suspecting that I have another connection to the database open within my application and that might be stopping the update until the application closes. However, I'm still not understanding why the call to Commit isn't updating the database at the end of the transaction block.

Related

Oracle PLSQL Print Bulk Batch Output After Each Batch Completion [duplicate]

I have an SQL script that is called from within a shell script and takes a long time to run. It currently contains dbms_output.put_line statements at various points. The output from these print statements appear in the log files, but only once the script has completed.
Is there any way to ensure that the output appears in the log file as the script is running?
Not really. The way DBMS_OUTPUT works is this: Your PL/SQL block executes on the database server with no interaction with the client. So when you call PUT_LINE, it is just putting that text into a buffer in memory on the server. When your PL/SQL block completes, control is returned to the client (I'm assuming SQLPlus in this case); at that point the client gets the text out of the buffer by calling GET_LINE, and displays it.
So the only way you can make the output appear in the log file more frequently is to break up a large PL/SQL block into multiple smaller blocks, so control is returned to the client more often. This may not be practical depending on what your code is doing.
Other alternatives are to use UTL_FILE to write to a text file, which can be flushed whenever you like, or use an autonomous-transaction procedure to insert debug statements into a database table and commit after each one.
If it is possible to you, you should replace the calls to dbms_output.put_line by your own function.
Here is the code for this function WRITE_LOG -- if you want to have the ability to choose between 2 logging solutions:
write logs to a table in an autonomous transaction
CREATE OR REPLACE PROCEDURE to_dbg_table(p_log varchar2)
-- table mode:
-- requires
-- CREATE TABLE dbg (u varchar2(200) --- username
-- , d timestamp --- date
-- , l varchar2(4000) --- log
-- );
AS
pragma autonomous_transaction;
BEGIN
insert into dbg(u, d, l) values (user, sysdate, p_log);
commit;
END to_dbg_table;
/
or write directly to the DB server that hosts your database
This uses the Oracle directory TMP_DIR
CREATE OR REPLACE PROCEDURE to_dbg_file(p_fname varchar2, p_log varchar2)
-- file mode:
-- requires
--- CREATE OR REPLACE DIRECTORY TMP_DIR as '/directory/where/oracle/can/write/on/DB_server/';
AS
l_file utl_file.file_type;
BEGIN
l_file := utl_file.fopen('TMP_DIR', p_fname, 'A');
utl_file.put_line(l_file, p_log);
utl_file.fflush(l_file);
utl_file.fclose(l_file);
END to_dbg_file;
/
WRITE_LOG
Then the WRITE_LOG procedure which can switch between the 2 uses, or be deactivated to avoid performances loss (g_DEBUG:=FALSE).
CREATE OR REPLACE PROCEDURE write_log(p_log varchar2) AS
-- g_DEBUG can be set as a package variable defaulted to FALSE
-- then change it when debugging is required
g_DEBUG boolean := true;
-- the log file name can be set with several methods...
g_logfname varchar2(32767) := 'my_output.log';
-- choose between 2 logging solutions:
-- file mode:
g_TYPE varchar2(7):= 'file';
-- table mode:
--g_TYPE varchar2(7):= 'table';
-----------------------------------------------------------------
BEGIN
if g_DEBUG then
if g_TYPE='file' then
to_dbg_file(g_logfname, p_log);
elsif g_TYPE='table' then
to_dbg_table(p_log);
end if;
end if;
END write_log;
/
And here is how to test the above:
1) Launch this (file mode) from your SQLPLUS:
BEGIN
write_log('this is a test');
for i in 1..100 loop
DBMS_LOCK.sleep(1);
write_log('iter=' || i);
end loop;
write_log('test complete');
END;
/
2) on the database server, open a shell and
tail -f -n500 /directory/where/oracle/can/write/on/DB_server/my_output.log
Two alternatives:
You can insert your logging details in a logging table by using an autonomous transaction. You can query this logging table in another SQLPLUS/Toad/sql developer etc... session. You have to use an autonomous transaction to make it possible to commit your logging without interfering the transaction handling in your main sql script.
Another alternative is to use a pipelined function that returns your logging information. See here for an example: http://berxblog.blogspot.com/2009/01/pipelined-function-vs-dbmsoutput.html When you use a pipelined function you don't have to use another SQLPLUS/Toad/sql developer etc... session.
the buffer of DBMS_OUTPUT is read when the procedure DBMS_OUTPUT.get_line is called. If your client application is SQL*Plus, it means it will only get flushed once the procedure finishes.
You can apply the method described in this SO to write the DBMS_OUTPUT buffer to a file.
Set session metadata MODULE and/or ACTION using dbms_application_info().
Monitor with OEM, for example:
Module: ArchiveData
Action: xxx of xxxx
If you have access to system shell from PL/SQL environment you can call netcat:
BEGIN RUN_SHELL('echo "'||p_msg||'" | nc '||p_host||' '||p_port||' -w 5'); END;
p_msg - is a log message
v_host is a host running python script that reads data from socket on port v_port.
I used this design when I wrote aplogr for real-time shell and pl/sql logs monitoring.

How to use the functionality of the new Data.DBXOdbc unit included in Delphi 10.2 Tokyo

I used to work with Delphi 2007 and I was able to connect to many different database mgmt. systems using the Open source dbExpress drivers of Vadim V. Lopushanskiy. Now I work with 10.2 Tokyo. I'm happy that I can connect to Access again after reading this thread. And to SQL Server, but I don't use it ;-)
I'm experiencing problems though esp. when trying to connect to Oracle and Sqlite3 in the kind of flexible way I'm used to. Let me focus on the problems I have with connecting to Sqlite. I use the SQLite ODBC Driver developed by Christian Werner. No success, i.e. I get this as error: 'unsupported info option 131'. It's unfortunate that there's so little documentation available how to use the functionality of the new Data.DBXOdbc unit. Hopefully Embarcadero will do something about this.
Below my code, from a DataModule unit with a TSQLConnection on it which is assigned to TCustomConnection called FDatabase. This is the code for connecting to Sqlite:
with (FDatabase As TSQLConnection) do
begin
VendorLib := 'sqlite3odbc.dll';
Params.Values['TRIMCHAR'] := 'True'; // 'Trim Char'
end;
if aUseDsn then
ConnStr := 'DSN=' + aDbxProviderStr + ';'
else
begin
// Work the database qualifier into a connection string.
// Assume that aDbxProviderStr contains that database qualifier.
Connstr := 'DRIVER=SQLite3 ODBC Driver;Database=';
Connstr := Connstr + aDbxProviderStr + ';';
end;
if (aUsername <> '') and (aPassword <> '') then
begin
ConnStr := ConnStr + 'UID=' + aUsername + ';PWD=' + aPassword + ';';
end;
with (FDatabase As TSQLConnection) do
begin
Params.Values[DATABASENAME_KEY] := '?';
Params.Values[CONNECTION_STRING] := ConnStr + 'coEnableUnicode=0;';
end;
(FDatabase As TSQLConnection).Open;
I hope somebody out there can show us how to overcome this problem.
Ok, it's apparently possible to use a TSQLConnection object for connecting to different databases, e.g. Sqlite, MS-Access or Oracle. Of course one at the time. You need to assign all the properties mentioned in the dbxconnections.ini file to the TSQLConnection object, starting with the DriverName property. This ini file can be found in folder C:\Users\Public\Documents\Embarcadero\Studio\dbExpress\19.0
I was hoping that I could connect to the 3 mentioned database management systems (dbms) via Odbc, but I gave up. After all, the error 'unsupported info option 131' is from the ODBC driver and this error is difficult to solve for me.
Instead of adding 1 TSQLConnection object to my data module, I added 3 of them. One for each dbms. When I want to connect, I assign it to the SQLConnection property of my TSQLDataset object - also found on the data module. Before I open the connection, I change only a few properties of the relevant TSQLConnection object, i.e. only the Params.Values. This way, you end up with a more stable application. The memory used by the TSQLConnection and TSQLDataset objects is freed when the data module is freed.
It means that I only use the Data.DBXOdbc unit when I want to connect to MS Access. For Oracle, I use the Oracle driver included in Delphi. And for Sqlite, I use the Sqlite driver. The main thing is that I can easily switch to another database ...

simple way to output messages from a stored procedure in Teradata

Using SQL*Assistant:
REPLACE PROCEDURE test_proc()
BEGIN
DECLARE l_msg varchar(128);
set l_msg = 'test';
-- PRINT is not supported
--print l_msg;
-- debug is recognized as a special token, but doesn't work
--debug l_msg;
-- this does nothing
--SIGNAL SQLSTATE '02000';
END;
Is there a simple way to output a text during a procedure execution, aside writing to a log table?
TD 14.xx
EDIT:
Not trying to handle exceptions, but rather send text messages to the client, as the procedure progresses, regardless of the state/condition, similar to PRINT (Sybase), DMBS_OUTPUT(Oracle), DEBUG(SQL Server).

Opening and closing database connections in multiuser environment

This is a multiuser application (multithreaded) where various departments will access their own database.The database is SQLite and I am using FireDac.For each department I have assigned a separate ADConnection so I dont get any unexpected locks.
Which connection will be activated (active) depends solely on the number produced by the ADQuery3. This is done on MainForm Show because it needs to be this way (which gets shown after successfull login). I would like to be able to close every connection on FormClose but I run into some bad issues when multiusers use the same database and log in and out.So I would like to ask if this is the right programming logic I am doing or this could be done in a better way?
Also I have never used this many begin end else and I am wondering how to proceed with this?
I mean when I need to check the if the number of another department came up, like
if DataModule1.ADQuery3.FieldByName('DEPARTMENT').AsString = '12' where does the next ELSE come up?
procedure TMainForm.FormShow(Sender: TObject);
begin
if DataModule1.ADQuery3.FieldByName('DEPARTMENT').AsString = '13'
then begin
try
if DataModule1.1_CONNECTION.Connected = true then
DataModule1.1_CONNECTION.Connected := False
else
DataModule1.1_CONNECTION.DriverName:= 'SQLite';
DataModule1.1_CONNECTION.Params.Values['Database']:= ExtractFilePath(Application.ExeName)+ 'mydatabase.db';
DataModule1.1_CONNECTION.Connected := true;
DataModule1.ADTable1.TableName :='DEPT_13';
DataModule1.DEPT_13.Active:=True;
cxGrid1.ActiveLevel.GridView := DEPT_13;
except
on E: Exception do begin
ShowMessage('There was an error... : ' + E.Message);
end;
end;
end;

What is correct Delphi syntax to set a SQL query parameter value from a stream?

I use a web site that requires a logon and at one point shows a jpg using the http
<img src="https://www.theurl.com/pictures/Photo.ashx?theid=221">
I want to use Delphi to download this image (and others by using a different parameter) and store it in a SQLite database.
I am able to take a jpg file from my HDD and store it in the database with..
procedure TForm1.Button1Click(Sender: TObject); //this works
begin
DISQLite3Database1.DatabaseName := 'C:\testphoto.db';
DISQLite3Database1.Open;
try
Query1.Close;
Query1.selectSQL := ('insert into StudentPhotos(id,photo)
values(''sally'', :photo)');
Query1.Params.ParamByName('photo').LoadFromFile
('C:\Users\Admin\Documents\testpic2.jpg',ftGraphic);
Query1.Open ;
finally
DISQLite3Database1.close;
end;
end;
I am also able to download the image from the website into a file using the following (after running code that handles the logon)
procedure TForm1.Button2Click(Sender: TObject);
var
Strm: TMemoryStream;
HTTP: TIdHTTP;
LHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
try
http:= TIdHTTP.create; //make an http component
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
Strm := TMemoryStream.Create;
HTTP.IOHandler:=LHandler;
HTTP.HandleRedirects := true;
HTTP.Request.UserAgent := 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'; try
Http.Get('https://www.TheUrl.com/picures/Photo.ashx?theid=221' , Strm);
Strm.Position := 0;
Strm.SaveToFile('C:\Users\Admin\Documents\testpic2.jpg');
except
on e:Exception do
begin
ShowMessage(E.ClassName+' error raised, with message : '+E.Message);
showmessage('could not download file');
end;
end;
finally
http.Free;
LHandler.Free ;
Strm.free;
end;
end;
However I'd prefer not to save each file on the clients hard drive and then read it back in to save it into the database as that will be quite slow.
Question
What is the correct syntax to combine both of the above procedures so that I can download into a stream and then pass the stream directly into the query parameter ready to save it in the database?
Note I am using DISQLIte3 but the query methods/properties are similar to other components.
Most classes that have a LoadFromFile also have a LoadFromStream. Have you tried that?
ie.
Query1.Params.ParamByName('photo').LoadFromStream(Strm,ftGraphic);
# Joe Meyer - Yes I saw that link, it doesn't have anything to do with databases. Like most things I have seen over the last two days it only deals with blobs going to and from images
#bummi & HeartWare
I tried loads of different combinations of ... ParamByName('photo').LoadFromStream()... using Tfilestream and TmemoryStream but kept getting incompatible type errors, maybe because I didn't know what TBlobType to use when dealing with jpgs as opposed to bitmaps.
I copied exactly what you proposed into my first procedure to get
begin
DISQLite3Database1.DatabaseName := 'C:\Users\Admin\Documents\RAD Studio\Projects\sqlite with photos\testphoto.db';
DISQLite3Database1.Open;
try
strm := TmemoryStream.Create;
strm.LoadFromFile('C:\Users\Admin\Documents\RAD Studio\Projects\sqlite with photos\testpic2.jpg');
Query1.Close;
Query1.selectSQL := ('insert into StudentPhotos(id,photo) values(''sally'', :photo)');
Query1.Params.ParamByName('photo').LoadFromStream(Strm,ftGraphic);
Query1.Open ;
finally
strm.Free ;
DISQLite3Database1.close;
end;
end;
... and it worked first time!
I think sometimes when developing one can't see the wood for the trees.
I should be able to work out the rest now, Thanks to you both

Resources