Setting a relative path to sqlite database with Delphi and Firedac - sqlite

In replacement of my previous question which was confusing and poorly formulated, here is the "real" question.
I would like to known how to set, with Firedac, at runtime, a relative path to a sqlite database located in a subfolder of my application folder.
As Jerry Dodge stated :
Any application should never rely on writable data in the same directory anyway. Also, even if you did, you should make sure all your paths are relative to the application at least.
At the moment, the application I have in mind is portable and I would like the database file to be stored in a sub-folder of the main exe folder.
On the Form.Create event of my main form, is used first
path := ExtractFilePath(Application.ExeName);
And in then for FDConnection :
with FDConnection1 do begin
Close;
with Params do begin
Clear;
Add('DriverID=SQLite');
Add('Database='+path+'Data\sqlite.db');
end;
Open;
end;
I keep on getting an error saying "unable to open database file".
I don't want to set the path to the database file in the FiredDac Connection Editor because then it would be absolute and bound to my machine, right ?
How could I set this path to the database file so that it would work in any configuration, wherever the user puts the application folder ?
Thank you all in advance
Math

As I found my own solution, I decided to post it here for future users who might encounter the same problem (that is to say a Delphi beginner level and the need to link a database file relative to their project exe file).
FIRST STEP was to add a data module to the project. This was done by going to File -> New -> Other -> Delphi Files -> Data Module
SECOND STEP Once the data module added to the project, as my main Application Form makes a call to the database on creation, I had to make sure the Data Module was created first. To achive that, I went to Project -> Options -> Forms and dragged the datamodule in first position of the list of auto created Forms
THIRD STEP was to drop a FDConnection on the datamodule and set all parameters EXCEPT database file.
FOURTH STEP was to add an OnCreate event to the datamodule, to specify the path to the database relative to the application exe and connect. It was done like this :
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
path := ExtractFilePath(ParamStr(0));
FDConnection.Params.Add('Database='+path+'Database\sqlite.db');
FDConnection.Connected := True;
end;
FIFTH AND FINAL STEP was to add the datamodule to the uses clause of all other units that needed a connection to the database.
I realize that this solution is far from perfect and that, as very experienced users already stated, storing the database in the same folder (or a sub-folder) as the main application Exe is not a good solution.
Also, I decided to connect to the database on DataModule creation, but another solution could be to connect on demand before triggering the queries and then disconnect. That's up to you and your needs
Thanks to all for your help, tips and advises
Math
PS : please notice I did not check my answer as accepted as the best answer, would not be fair right :-)

Introduction
IMHO
Database path and server name should not be hardcoded in applivcation.
Why ?
When you work on a project, you need to do many things on database connection, setting datasets, query etc. Usualy this is done on working database. Then server name and database path are different from those of the real database.
You should be able to set up server name and path to database easy and without to compile the project. This allows to set properly database connection params on a random computer.
Solution :
Setup the database connection component on design time, do not create it in runtime. Setup all parameters including server name , database path, charset etc. to your working copy of database. This will allow you to set up the other components associated with this database on design time.
(In your answer I see you have done almost the same.)
Save server name, database path and any other parameter you want, to an exterrnal resource, ini file, windows registry or something else. Then get these parameters when application started or before connect to database.
In your case, you use local server and the same path as application, so you don't need to store nothing.
Regarding the question
The code :
with FDConnection1 do begin
Close;
with Params do begin
Clear; <-- this removes all parameters
Add('DriverID=SQLite');
Add('Database='+path+'Data\sqlite.db');
end;
Open;
end;
removes all other parameters except DriverID and Database. Probably the error arise from that.
If you already setting all parameters in FDConnection:
Do not use:
FDConnection.Params.Add('Database='+path+'Data\sqlite.db');
This will add new parameter with the same name, but connection will use the first one.
This explains why everything works in your answer, because you did not set a parameter 'Database' on design time:
THIRD STEP was to drop a FDConnection on the datamodule and set all
parameters EXCEPT database file.
Instead use :
FDConnection.Params.Database := 'Database='+path+'Data\sqlite.db';
You may use this for example in
OnDataModuleCreate or FDConnectionBeforeConnect events
I hope this will be useful.

In android, I couldn't compile with fdConnection opened in design time, then certify it's closed. Your first line didn't work for me, but about takes the goal:
using following expression, it works for me:
FdConnection1.Params.Values['Database'] := GetHomePath
+ PathDelim + 'sqlite.db';
Look that I didn't use subfolder 'Data'. You can try simple first.

If your resources are light and read-olny for end-user, you could pack them direct into exe file. This would give you really portable application, which your user can run from arbitrary place, even from usb flash.
Go to Project->Resources and Images menu, add there you files.
You can access them in runtime with TStream:
var
s: TStream;
{.....}
s:=TResourceStream.Create(hinstance, 'myfile_1', RT_RCDATA);
Some resources you can handle direct in memory. As for sqlite database, you could copy it from app resources to user's documents path:
MyAppPath := Tpath.GetDocumentsPath+'\MyAppName'; // you need System.IOUtils in uses
If not DirectoryExists(MyAppPath) then CreateDir(MyAppPath);
MyDBPath := MyAppPath+'\Data\sqlite.db';
If not FileExists(MyDBPath) then begin
FL:=TFileStream.Create(MyDBPath ,fmCreate);
FL.CopyFrom(s,0); // this will copy your db into 'sqlite.db' file
end;

Related

Delphi TFDTable SqLite problems

I deployed a sample.s3db SqLite to my Delphi android project.
set folder to:
fdconnection1.params.Database := system.ioutils.TPath.Combine( system.ioutils.TPath.GetDocumentsPath, 'sample.s3db');
in FDConnection1BeforeConnect event.
in Project-Deployment, set folder to assets\internal\ for Android
However, when I run the app, apparently it can find the physical file, but it says my tablename "Customers" is not found.
so in the BeforeConnect code, if I add a CREATE TABLE SQL create code if not exists, then everything works well but of course the table is empty (whereas my deployed sample.s3db is not empty) ... which proves the FILE sample.s3db was found and not corrupted, but somehow the Table was not found.
Even tried to change in BeforeConnect
FDConnection1.Params.Values['OpenMode'] := 'ReadWrite';
but it still doesn't work.
Any ideas the table name does not work?
I tried to change table name to all caps, in case Android is case-sensitive but it also does not work.
I recommend deleting your app from the android. This will clear out your data. Otherwise your new sqlite file is not deployed if the file already exists. This could happen for instance if you made an error in the path at one point in time so sqlite just created an empty database.
Your connection string looks correct and so does your destination path. Just to be safe, you may also want to clear out your other values for your connection.
fdConnection1.ConnectionDefName:= '';

sqlite database location node-red

I have a Node-RED flow. It uses a sqlite node. I am using node-red-node-sqlite. My OS is Windows 10.
My sql database is configured just with name "db" :
My question is, where is located the sqlite database file?
I already search in the following places, but didn't found:
C:\Users\user\AppData\Roaming\npm\node_modules\node-red
C:\Users\user\.node-red
Thanks in advance.
Edit
I am also using pm2 with pm2-windows-service to start Node-RED.
If you don't specify a full path to the file in the Database field it will create the file in the current working directory for the process, which will be where you ran either node-red or npm start.
Use full path location with file name.
It should work i guess.
This isn't a valid answer, just a workaround for those who have the same problem.
I could't find my database file. But inside Node-RED everything worked just great. So. this is what I have done as a workaround:
In Node-RED, make some select nodes to get all data from tables
Store the tables values somewhere (in a .txt file or something like that)
Create your database outside Node-RED, somewhere like c:\sqlite\db.db. Check read/write permissions
Create the tables and insert the values stored from old database
In Node-RED, inside "Database", put the complete path of the database. For example, c:\sqlite\db.db
In my case this was easy because I only had two database with less than 10 rows.
Hope this can help others.
Anyway, still waiting for a valid answer :)

Using MagicalRecord to prepopulate a database (swift). -wal journal created, however, data not copied to database

(Forgive me since I am not use to posting here. Will do my best)
I am working on an iOS application that will be using a pre-populated database. In my workspace, I have a project for the iOS app and a separate command line tool project to populate the database. I have dragged the database file from the "/Library/Application Support" folder into my iOS project (without using the "Copy Items If Needed"). So when there is a change in the data or additional data is required, I can just run the command line tool to pre-populate the data. From there I will remove the app from the simulator and do a clean. When I run the app, I would think everything would be ok.
It was driving me crazy for the longest but sometimes, I don't see the changes reflected after I remove the app, clean the project and then run. It seems the only way I can get this to work is, after I run the command line tool to pre-populate the database, I have to open the database file using Base, SQLiteStudio or Firefox's sqlite add-on. Once I do that, it seems to work.
When I look in finder, I do see the files .sqlite, sqlite-shm and sqlite-wal. Before opening the database file, I see that the wal file is the biggest (for now it's 2mb). Once I open the file using Base for example, and then close it, the sqlite file is now 2mb.
When the command line is about to finish, I have tried running PRAGMA statements on the file (vacuum, wal-checkpoint) but those did not work. What am I missing here. I also tried using NSManagedObjectContext.MR_defaultContext.saveToPersistentStoreAndWait
I am using the following code to setup and save.
MagicalRecord.setupCoreDataStackWithAutoMigratingSqliteStoreNamed("callithome.sqlite")
MagicalRecord.saveUsingCurrentThreadContextWithBlockAndWait({(context)->Void in
println("Data saved, I hope")})
MagicalRecord.cleanUp()
Any help would be appreciated
You need to enable the DELETE pragma mode for your sqlite store. This will not create the -wal and -shm files. This will make it easier for you to have a single file for use at your pre-populated data store. You will need to use MagicalRecord 3.0 with some of the extra abilities to add custom options to your stores if you'd like o go that route. However, you can still add a store to a coordinator and have that store configured with the proper pragma option as well. That is, MagicalRecord is not necessary if you use normal Core Data.

Best practices place to put URL that configs my app?

We have a Qt app that when it starts tries to connect to a servlet to get config parameters that it needs to keep running.
The URL may change frequently because we have to test the application in several environments. Right now (as a temporary solution) the URL is a constant in source code, but it is a little bit ugly.
Where is the best place to mainting this URL, so that we do not need to change the source code every time I want to change the environment target?
In a database table maybe (my application uses a SQLite DB), in a settings file, or in some other way?
Thank you for you replies.
You have a number of options:
Hard coded (like you have already)
Run-time user input
Command line arguments
QSettings
Read from a bespoke file as text.
I would think option 3 would be the most simple to implement without being intrusive, but it does depend on what kind of application you have.
I would keep the list of url in a document, e.g. a XML, stored in a central, well known place, e.g. a known web server, and hardcode the url of the known place in the app.
The list could then be edited externally without recompiling your app;
The app would at startup download and parse the list, pointing to the right servlet based upon an environment specified as a command line parameter.

I can't write to a SQLLite database in my AIR app

I am publishing an AIR app in debug mode using FlashDevelop and have included a database in the files/folders to be published.
When the app first launches it checks whether there is an instance of this db in the applicationStorageDirectory, if there isn't it copies the included one from the applicationDirectory to the applicationStorageDirectory.
This should mean that the referenced database dbFile = File.applicationStorageDirectory.resolvePath(DB_FILE_NAME); should now be writable, however when i run the app i can read the records from the table but when i attempt to write using an SQL statement I get an SQLError: 'Error #3122: Attempt to write a readonly database'.
I know that this would be thrown if i was attempting to write to the read only location of the applicationDirectory but i'm certainly using the File.applicationStorageDirectory location which should (as far as i know) be writable.
The location of the db on my Windows 7, 64bit = C:\Users\sean.duffy\AppData\Roaming\FishFightAppData\Local Store\db this is found using the dbFile.nativePath property so again i'm sure i should be able to update the db.
Anyone got any ideas? I have tried everything i could think of and searched all over but the only common cause seems to be when people try to write to the asplicationDirectory and not the storage directory....
UPDATE::
My bad - have just realised that i've misread the API of the 3rd party library i'm using! I should have been calling executeModify(statement) which can modify the contents of the db, instead i'm calling execute(statement) which doesn't/can't overwrite the db.
The source code is compiled into a swc and there was no documentation to point out you needed to use executeModify, although i should have guessed from the name i suppose!
Sorry about that and thanks for your help
(As a public courtesy to get this off the unanswered list, I am reposting the apparent solution. As usual, the asker is more than welcome to ignore mine and post it themselves and accept their own answer.)
In this API, you need to call executeModify(statement), not execute(statement). The latter does not overwrite the database.

Resources