Use SQLite3 Temporary database with QT4 QSQLDatabase - c++

http://www.sqlite.org/inmemorydb.html
Mentions the ability to create a temporary database that will be stored in memory until a file is necessary (if at all). It will also remove that file when finished automatically. This is achieved by providing a blank database name "".
rc = sqlite3_open("", &db);
I'm trying to do this within a QT4 based application using the QSQLDatabase
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("");
bool ok = db.open();
Results in a failure to open the database.
I'm aware of the :memory: option and using it is much faster in our application for small datasets. I would prefer something that will drop back to a file when necessary since we may have some large datasets.
I am making the assumption that allowing the database engine to cache to file when necessary will be more efficient that just letting the OS page the memory in and out.
I'm open to alternatives with the following requirements:
Fast insert and lookup
No file once application is closed
Update
After going through some of the suggested SQLite performance suggestions I now have acceptable performance when using a file (TRANSACTIONS!).
I have not been able to figure out how to use sqlite3's built-in temporary file functionality.
I'm trying to use a QTemporaryFile, but for some reason they won't auto-delete the way the documentation implies they should. I have some more experimenting to do.

TL;DR - NO, you cannot give an empty string as a database name to sqlite3 using Qt. (see Edit 3).
Original answer
One possibility is to use the backup option in SQLite for an in-memory database.
But since it sounds like an optimization issue, you should read this SO answer which goes into a fair amount of detail about how you can speed up your database and disconnect it from the disk temporarily using PRAGMA synchronous = OFF and/or PRAGMA journal_mode = MEMORY. You might also want to limit the size of your in-memory pages by using the PRAGMA cache_size.
Edit 1: After reading your question again I realize now that you are asking to store overflow data to disk and want SQLite to just manage that so my first paragraph is not useful.
Edit 2: Added the PRAGMA cache_size suggestion.
Edit 3: Answering the meat of the question:
Your link to the other SO answer about SQLite optimization was very helpful, but still does not answer the meat of the question. ie how to use the sqlite3 built-in temp file functionality through the QT interface. Gavin S.
Ok, but the answer to that is no, and here's why.
If you take a look at the SQLite driver in Qt source and in particular the QSQliteDriver::open function it looks like this:
/*
SQLite dbs have no user name, passwords, hosts or ports.
just file names.
*/
bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts)
{
if (isOpen())
close();
-> if (db.isEmpty())
-> return false;
bool sharedCache = false;
int openMode = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, timeOut=5000;
QStringList opts=QString(conOpts).remove(QLatin1Char(' ')).split(QLatin1Char(';'));
foreach(const QString &option, opts) {
if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT="))) {
bool ok;
int nt = option.mid(21).toInt(&ok);
if (ok)
timeOut = nt;
}
if (option == QLatin1String("QSQLITE_OPEN_READONLY"))
openMode = SQLITE_OPEN_READONLY;
if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE"))
sharedCache = true;
}
sqlite3_enable_shared_cache(sharedCache);
-> if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL) == SQLITE_OK) {
sqlite3_busy_timeout(d->access, timeOut);
setOpen(true);
setOpenError(false);
return true;
} else {
setLastError(qMakeError(d->access, tr("Error opening database"),
QSqlError::ConnectionError));
setOpenError(true);
return false;
}
}
The function sqlite3_open you are trying to call will never be called with an empty string as an argument because of the early return condition on lines 530 and 531 unless you patch the Qt driver on that particular line of code.

Related

How to unit test a void function in C++

I am working on a hobby project mainly to learn cpp unit testing and database programming. However I am a little bit lost & confused about how should I write my code for proper testing. I tend to write a lot of void functions for my cpp projects. But now I can not figure out how should I test those functions. I have been succeeded in testing non-void functions cause they return something which can be easily tested against a value.
Ami I doing things in an unprofessional way? Should I avoid void functions as much as possible so that I can test those functions ? Or I am missing something ? For example how would I be able to test this function -
database.cpp
#include "database.hpp"
#include <sqlite3.h>
#include <iostream>
#include "spdlog/sinks/basic_file_sink.h"
// Creating the logging object
auto logger = spdlog::basic_logger_mt("appnotex", "../data/appnotexlog");
void Database::createDb(const char *dbname) {
// Creating the database file
sqlite3 *datadb;
int status = sqlite3_open(dbname, &datadb);
// checking for errors
if (status == SQLITE_OK) {
logger->info("------------ New Session ----------");
logger->info("Connected to Database Successfully");
} else {
std::string errorMessage = sqlite3_errmsg(datadb);
logger->info("Error: " + errorMessage);
}
If Needed
I am using Google Test framework
My whole project code hosted - here
Update
I have tried this one is this approach of testing the above method correct ?
databaseTest.cpp
TEST(DatabaseTest, createDbTest) {
const char *dbfilename = "../data/test/data.db";
const char *tbname = "DataTest";
Database *db = new Database();
std::ifstream dbfile("../data/test/data.db");
bool ok = false;
if (!dbfile.is_open())
ok = false;
else
ok = true;
EXPECT_TRUE(ok);
}
The problem is not so much in the function returning void. Think about how it signals errors and make sure all cases (success and failures) are tested, simple as that.
However, I don't see any error signalling at all there, apart from logging it. As a rule of thumb, logging should only be used for post-mortem research and the like. So, if logging completely fails, your program can still run correctly. That means, nothing internally depends on it and it is not a suitable error handling/signalling mechanism.
Now, there are basically three ways to signal errors:
Return values. Typically used in C code and sometimes used in C++ as well. With void return, that's not an option, and that is probably the source of your question.
Exceptions. You could throw std::runtime_error("DB connect failed"); and delegate handling it to the calling code.
Side effects. You could store the connection state in your Database instance. For completeness, using a global errno is also possible, but not advisable.
In any case, all three ways can be exercised and verified in unit tests.

When connect to the database I get an error `driver not loaded`

First: I am using Qt v5.3.1 with MinGW 4.8.2, and Window 7 32bit platform.
When run my application in windows 7, I found it is working fine in connect to database only if Qt environment is installed, also when move the same application to another platform like windows xp by Virtual PC, unfortunately I found the connection to database fail too, and an error message appears driver not loaded, but the application works fine but without connection to database.
My attempts:
I have used QSqlDatabase::drivers() to check if sqlite supported
in the system, and the result was, sqlite database
supported with many other types.
I have used isValid() to check if there is a valid driver, but the
function return false, and this indicates that the database type is
not available or could not be loaded.
The following is the code that I use:
database.h
class database
{
public:
static QSqlDatabase db;
static QString dbPath;
database();
static void connect();
static bool openConnection();
static void CloseConnection();
static void removeDB();
};
database.cpp
QSqlDatabase database::db = QSqlDatabase::addDatabase("QSQLITE");
QString database::dbPath = "";
database::database(){
}
void database::connect(){
database::dbPath = "database.db";
database::db.setDatabaseName(database::dbPath);
}
void database::CloseConnection(){
database::db.close();
}
void database::removeDB(){
database::db.removeDatabase(database::db.defaultConnection);
}
Also I have checked if the database file exists or not, and also I have opened the connection to database.
database::connect();
if(QFile::exists(database::dbPath)){
if(database::db.open()){
ui->label->setText(ui->label->text() + "Connected.");
QSqlQuery qry;
qry.prepare("SELECT * FROM users");
qry.exec();
while(qry.next()){
ui->listWidget->addItem(qry.value("username").toString());
ui->listWidget->item(ui->listWidget->count()-1)->setData(Qt::UserRole, qry.value("id").toString());
}
database::CloseConnection();
}else{
ui->label->setText(ui->label->text() + "Failed to connect to database");
}
}else{
ui->label->setText(ui->label->text() + "Database file does not found");
}
I don't know what's the problem in the connection with database, everything is alright, and there is no missing files in my application, and the database file beside the executable file.
How to solve this problem ?
QSqlDatabase database::db = QSqlDatabase::addDatabase("QSQLITE");
This line is the problem. This is global code (code that gets run before main), which in turn attempts to load a Qt Plugin before a QCoreApplication instance has been created. This call will therefore fail.
Solution: leave it default-initialized and call addDatabase after creating a QCoreApplication.
Better solution: stop using global variables; you can open your DB connection in main and pass the connection handle to the classes which need it (also note that QtSql classes natively support the concept of a "connection name", so you can access that connection from everywhere, given the right name of the conection).
In Windows XP, \Program Files is not writable by normal users.
(Neither is it in Windows 7, but depending on the configuration, some paths might be redirected.)
Use QDesktopServices::DataLocation (Qt 4) or QStandardPaths::DataLocation (Qt 5).
database::dbPath = "database.db" means the database file beside the executable file or in the main directory of application"
This statement is false. The path you gave is relative to the current working directory, not to the location of the executable file. If the working directory and the executable's path happen to be the same, you merely ran into a coincidence that can't be depended on. Never mind that the executable's location is not writable, so there's no point in storing any variable data there!
You need to store that database somewhere else, and you must be explicit about where it's stored.

Writing an SQLite manager class: viable approach?

I'm learning SQLite and C++ within the Qt framework. As a learning project I am doing a simple image viewer which enables the user to tag images with keywords, categories, comments and ROI (for some later OpenCV functionality). It's a pretty simple database with some primary tables and some relational tables.
I think I've got the basics down and early tests show that my record data is being stored but in writing the methods to manage the database it looks like I am going to end up with a great many methods, particularly when I start to add in searching, sorting, deleting, etc.
This leads me to ask if I am going about this the correct way. I can see how some of my query logic (for search, sort) is probably better off being in a different "controller" type class and that all this manager class needs to do is handle the basic creation and deletion tasks, and just return a query object in response to a SELECT statement passed in as a string.
So, am I going about this in a reasonable way?
Manager methods so far:
bool openDatabase(QString name);
void closeDatabase();
bool createTables();
bool addKeyword(QString keyword);
bool addCategory(QString category);
bool deleteKeyword(QString keyword);
bool deleteCategory(QString category);
bool addROI(int x, int y, int width, int height);
bool deleteROI(int id);
bool addImage(QString name, QString path, QByteArray image, QByteArray thumb);
You probably should be using Qt's Model View Framework. The important classes there are QSqlQueryModel, QSqlTableModel and QSqlRelationalTableModel. To keep your UI isolated from the database structure, a reasonable approach would be to add view models for the particular use cases you have. You can then easily link those to, say, a Qt Quick based user interface.
There's nothing particularly wrong with function-oriented interface that you propose, except that it requires a lot of boring glue code to use it for user interfaces. It's best to factor such glue code as a proxy view model, since you're working to a documented API that can be then easily picked up by coworkers.
I have seen a DatabaseManager class somewhere and modified it. This is the insert function :
bool DatabaseManager::insert(const QString &tableName, const QString& columns, const QString &value)
{
bool ret = false;
if(db.isOpen()){
QSqlQuery query;
ret = query.exec( QString("INSERT INTO %1 (%2) VALUES(%3)").arg(tableName, columns, value) );
}
return ret;
}
You give it your table's name, the columns you want to fill and then the values. This is an example where I call it :
if( !dm.insert("Product", "ID, Name, Price, Notes, Category",
"'1', 'A DVD','10€', 'Some Notes', 'DVD'") )
{
//Note that each value is surrounded by an apostrophe
//Now whatever you want to
}

CRecordset::snapshot doesn't work in VS2012 anymore - what's the alternative?

Apparently, in VS2012, SQL_CUR_USE_ODBC is deprecated. [Update: it appears that the cursors library has been removed from VS2012 entirely].
MFC's CDatabase doesn't use it anymore (whereas it was the default for VS2010 and earlier versions of MFC), but instead uses SQL_CUR_USE_DRIVER.
Unfortunately, SQL_CUR_USE_DRIVER doesn't work properly with the Jet ODBC driver (we're interacting with an Access database). The driver initially claims to support positional operations (but not positional updates), but when an attempt is made to actually query the database, all concurrency models fail until the MFC library drops down to read-only interaction with the database (which is not going to fly for us).
Questions
Is this MS's latest attempt to force devs to move away from Jet based datasources and migrate to SQL Express (or the like)?
Is there another modality that we should be using to interact with our Access databases through VS 2012 versions of MFC/ODBC?(1)
See also:
http://social.msdn.microsoft.com/Forums/kk/vcmfcatl/thread/acd84294-c2b5-4016-b4d9-8953f337f30c
Update: Looking at the various options, it seems that the cursor library has been removed from VS2012's ODBC library. Combined with the fact that Jet doesn't correctly support positional updates(2), it makes "snapshot" mode unusable. It does appear to support "dynaset" as long as the underlying tables have a primary key. Unkeyed tables are incompatible with "dynaset" mode(3). So - I can stick with VS 2010, or I can change my tables to include an autonumber or something similar in order to ensure a pkey is available so I can use dynaset mode for the recordsets.
(1) e.g. should we be using a different open type for CRecordset? We currently use CRecordset::snapshot. But I've never really understood the various modes snapshot, dynamic, dynaset. A quick set of "try each" has failed to get a working updatable interface to our access database...
(2) it claims to when queried initially, but then returns errors for all concurrency modes that it previously claimed to support
(3) "dynamic" is also out, since Jet doesn't support it at all (from what I can tell from my tests).
I found a solution that appears to work. I overrode OpenEx the exact same way VS 2012 has it because we need that to call the child version of AllocConnect since it is not virtual in the parent. I also overrode AllocConnect as mentioned. In the derived version of CDatabase, try the following code:
MyCDatabase.h
BOOL OpenEx(LPCTSTR lpszConnectString, DWORD dwOptions = 0);
void AllocConnect(DWORD dwOptions);
MyCDatabase.cpp
BOOL MyCDatabase::OpenEx(LPCTSTR lpszConnectString, DWORD dwOptions)
{
ENSURE_VALID(this);
ENSURE_ARG(lpszConnectString == NULL || AfxIsValidString(lpszConnectString));
ENSURE_ARG(!(dwOptions & noOdbcDialog && dwOptions & forceOdbcDialog));
// Exclusive access not supported.
ASSERT(!(dwOptions & openExclusive));
m_bUpdatable = !(dwOptions & openReadOnly);
TRY
{
m_strConnect = lpszConnectString;
DATA_BLOB connectBlob;
connectBlob.pbData = (BYTE *)(LPCTSTR)m_strConnect;
connectBlob.cbData = (DWORD)(AtlStrLen(m_strConnect) + 1) * sizeof(TCHAR);
if (CryptProtectData(&connectBlob, NULL, NULL, NULL, NULL, 0, &m_blobConnect))
{
SecureZeroMemory((BYTE *)(LPCTSTR)m_strConnect, m_strConnect.GetLength() * sizeof(TCHAR));
m_strConnect.Empty();
}
// Allocate the HDBC and make connection
AllocConnect(dwOptions);
if(!CDatabase::Connect(dwOptions))
return FALSE;
// Verify support for required functionality and cache info
VerifyConnect();
GetConnectInfo();
}
CATCH_ALL(e)
{
Free();
THROW_LAST();
}
END_CATCH_ALL
return TRUE;
}
void MyCDatabase::AllocConnect(DWORD dwOptions)
{
CDatabase::AllocConnect(dwOptions);
dwOptions = dwOptions | CDatabase::useCursorLib;
// Turn on cursor lib support
if (dwOptions & useCursorLib)
{
::SQLSetConnectAttr(m_hdbc, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_ODBC, 0);
// With cursor library added records immediately in result set
m_bIncRecordCountOnAdd = TRUE;
}
}
Please note that you do not want to pass in useCursorLab to OpenEx at first, you need to override it in the hacked version of AllocConnect.
Also note that this is just a hack but it appears to work. Please test all your code to be sure it works as expected but so far it works OK for me.
If anyone else runs into this issue, here's what seems to be the answer:
For ODBC to an Access database, connect using CDatabase mydb; mydb.OpenEx(.., 0), so that you ask the system not to load the cursor library.
Then for the recordsets, use dynaset CMyRecordset myrs; myrs.Open(CRecordset::dynaset, ...).
Finally, you must make sure that your tables have a primary key in order to use dynasets (keysets).
Derive CDatabase and override OpenEx. In your derived class CMyDatabase, replace the call to AllocConnect to MyAllocConnect. Obviously, your MyAllocConnect function should call SQLSetConnectOption with the desired parameter:
// Turn on cursor lib support
if (dwOptions & useCursorLib)
{
AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_ODBC_CURSORS, SQL_CUR_USE_ODBC));
// With cursor library added records immediately in result set
m_bIncRecordCountOnAdd = TRUE;
}
Then use your CMyDatabase class instead of CDatabase for all your queries.

How do I use the registry?

In the simplest possible terms (I'm an occasional programmer who lacks up-to-date detailed programming knowledge) can someone explain the simplest way to make use of the registry in codegear C++ (2007).
I have a line of code in an old (OLD!) program I wrote which is causing a significant delay in startup...
DLB->Directory=pIniFile->ReadString("Options","Last Directory","no key!");
The code is making use of an ini file. I would like to be able to use the registry instead (to write variables such as the last directory the application was using)
But the specifics are not important. I'd just like a generic how-to about using the registry that's specific to codegear c++ builder.
I've googled this, but as usual with this type of thing I get lots of pages about c++ builder and a few pages about the windows registry, but no pages that explain how to use one with the other.
Use the TRegistry class... (include registry.hpp)
//Untested, but something like...
TRegistry *reg = new TRegistry;
reg->RootKey = HKEY_CURRENT_USER; // Or whatever root you want to use
reg->OpenKey("theKey",true);
reg->ReadString("theParam",defaultValue);
reg->CloseKey();
Note, opening and reading a ini file is usually pretty fast, so maybe you need to test your assumption that the reading of the ini is actually your problem, I don't think that just grabbing your directory name from the registry instead is going to fix your problem.
Include the Registry.hpp file:
#include <Registry.hpp>
Then in any function you have, you can write the following to read the value:
String __fastcall ReadRegistryString(const String &key, const String &name,
const String &def)
{
TRegistry *reg = new TRegistry();
String result;
try {
reg->RootKey = HKEY_CURRENT_USER;
if (reg->OpenKeyReadOnly(key)) {
result = reg->ReadString(name, def);
reg->CloseKey();
}
}
__finally {
delete reg;
}
return result;
}
So reading the value should be as easy as:
ShowMessage(ReadRegistryString("Options", "Last Directory", "none"));
You can use the following to write the value:
void __fastcall WriteRegistryString(const String &key, const String &name,
const String &value)
{
TRegistry *reg = new TRegistry();
try {
reg->RootKey = HKEY_CURRENT_USER;
if (reg->OpenKey(key, true)) {
reg->WriteString(name, value);
reg->CloseKey();
}
}
__finally {
delete reg;
}
}
Should be self explaining, remembering the try ... finally is actually really helpful when using the VCL TRegistry class.
Edit
I've heard that .ini files are stored in the registry in Windows, so if you want the speed advantage of ini files you should call them something else - like .cfg
This is something I've heard from an although reliable source, I haven't tested it myself.
Tim is right but an even simpler class to use is TIniRegFile but it is also more limited in what you can do.
Please see the documentation for the QSettings class from the Qt 4.5 library. It will allow you to load and store your program's configuration settings easily and in a cross-platform manner. The Windows implementation uses the Windows registry for loading and storing your program's configuration data. On other platforms, the platform's preferred, native mechanism for storing configuration data will be used. This is far better than interacting with the Windows registry directly, as you will not be tied to a specific platform.