qt bulk insert sqlite - sqlite

For a qt/qml project im doing inserts into a database.
Because there is plenty of data to send (200+ inserts/sec) I want to do it in bulk.
The inserts are into different tables.
I have the queries already prepared.
I made one method that should commit the queries after it reached 100 queries.
//in the constructor i laready say mdb.transaction() for the first run.
bool DatabaseWriter::executeQuery(QString insertQuery)
{
queryCounter ++;
qDebug() << QString::number(queryCounter);
QSqlQuery query(mydb);
if(!query.exec(insertQuery))
{
qDebug("%s.", qPrintable(mydb.lastError().text()));
qDebug() << "Something went wrong while running " + insertQuery + " from the database";
return false;
}
if(queryCounter > 100)
{
qDebug() << "Should commit";
if(!mydb.commit())
{
qDebug() << "commit failed";
}
mydb.transaction();
queryCounter = 0;
}
return true;
}
But mydb.commit() returns false.
What is the proper way to do a bulk insert?
I also tried:
if(query != ""){
query_to_commit += query +";";
insert_counter ++;
if(insert_counter > 100)
{
dbWriter.executeQuery("BEGIN TRANSACTION; "+query_to_commit+ " COMMIT;");
insert_counter = 0;
query_to_commit = "";
CustomLogger::log("Running bulk insert");
}
}
which generates a bulk transaction that works fine when executing it in my sqlite databsase but not when I do it with my code, in my code I get: Driver not loaded Driver not loaded. I do not get this error when running a single insert query.
To keep it simple, how can I run e.g.:
INSERT INTO settings (name, x, y) VALUES('somename', 100, 20)
INSERT INTO different_table(bla, x, y) VALUES('bla', 100, 20)
In one go instead of doing it in two.
Or How can i execute:
BEGIN TRANSACTION;
INSERT INTO settings (name, x, y) VALUES('somename', 100, 20)
INSERT INTO different_table(bla, x, y) VALUES('bla', 100, 20)
COMMIT;
The main goals is to not write 200 times a second but just do it in one go, if i do it 200 times a second my gui is blocked/buggy (on the same thread I gues).

I made a seperate query thead, my header file:
#ifndef QUERYTHREAD_H
#define QUERYTHREAD_H
#include <QThreadPool>
#include <QString>
#include <QSqlQuery>
#include <QSqlDatabase>
class QueryThread : public QRunnable
{
QString query_to_run;
static int queryNumber;
public:
QueryThread(QString query_list);
void run();
};
#endif // QUERYTHREAD_H
my cpp file:
#include "querythread.h"
#include <QDebug>
#include <windows.h> // for Sleep
#include <QSqlError>
#include "customlogger.h"
#include "databasewriter.h"
int QueryThread::queryNumber = 1;
QueryThread::QueryThread(QString query)
{
this->query_to_run = query;
}
void QueryThread::run()
{
{
// Make sure to have a unique database connection name by using the a static counter.
QString connectionName = "connection_name_"+QString::number(queryNumber ++);
QSqlDatabase mydb = QSqlDatabase::addDatabase("QSQLITE", connectionName);
mydb.setDatabaseName(DatabaseWriter::dirPath);
if(!mydb.open()){
CustomLogger::log("Cant open DB");
return;
}
QSqlQuery query(mydb);
if(!query.exec(query_to_run))
{
CustomLogger::log(qPrintable(mydb.lastError().text()));
CustomLogger::log("Something went wrong while running query: " + query_to_run);
}
mydb.close();
}
QSqlDatabase::removeDatabase("QSQLITE");
}
I start a new query with:
QueryThread *queryThread = new QueryThread(insertQuery);
// QThreadPool takes ownership and deletes 'queryThread' automatically
QThreadPool::globalInstance()->start(queryThread);
This implementation runs the queries on a seperate thread of the ui thread. It opens a connection for each insert though, so im not sure if this solution does not cause performance issues. so far it looks ok with 1.600.000 inserts in 16 hours.
If your doing a lot of inserts make so to put you database in wal mode to avoid the database from being locked.

Related

Using requests to verify command success

Lets say Typed Actor A needs to command Typed Actor B to do something. Actor A also needs to know if the command ran succesfully or not but does not want to block operation until this response arrives. My current working theory is that this is best satisfied with Requests. More specifically request(...).then
There is a nice example called "request.cpp" that I have been playing with. My challenge is that I don't really need actor B to return any data. I just need to know if the command was successful or not and if not what error was thrown.
So my question is two fold: 1) Am I correct in thinking that request(...).then is the correct mechanism to do what I want and 2) if so then can a request handle a response that has no data?
This is what I'm trying:
#include <chrono>
#include <cstdint>
#include <iostream>
#include <vector>
#include "caf/all.hpp"
using std::endl;
using std::vector;
using std::chrono::seconds;
using namespace caf;
using cell
= typed_actor<result<void>(get_atom, int32_t)>;
struct cell_state {
static constexpr inline const char* name = "cell";
cell::pointer self;
cell_state(cell::pointer ptr) : self(ptr) {}
cell_state(const cell_state&) = delete;
cell_state& operator=(const cell_state&) = delete;
cell::behavior_type make_behavior() {
return {
[=](get_atom, int32_t input) -> result<void> {
if (input != 5) { // Simulate command successful or not
return; // If successful, then return;
}
else {
return sec::unexpected_message; // If not then return error.
}
},
};
}
};
using cell_impl = cell::stateful_impl<cell_state>;
void multiplexed_testee(event_based_actor* self, vector<cell> cells) {
for (cell& x : cells) {
aout(self) << "cell #" << x.id() << " calling" << endl;
self->request(x, seconds(1), get_atom_v, static_cast<int32_t>(x.id()))
.then(
[=](void) {
aout(self) << "cell #" << x.id() << " -> " << "success" << endl;
},
[=](error& err) {
aout(self) << "cell #" << x.id() << " -> " << to_string(err) << endl;
});
}
}
void caf_main(actor_system& system) {
vector<cell> cells;
for (int32_t i = 0; i < 5; ++i)
cells.emplace_back(system.spawn<cell_impl>());
scoped_actor self{ system };
auto x2 = self->spawn(multiplexed_testee, cells);
self->wait_for(x2);
}
CAF_MAIN()
When I compile, I get an error on the empty return statement saying "return-statement with no value, in function returning caf::result<void>. Is there a better way to do this?
My backup plan is to change my command definition to just return a standard error and return sec::none if the operation was successful. But I'm afraid that approach violates the spirit of the whole optional-second-parameter for error conditions. How well am I thinking about all this?
Is there a better way to do this?
You had the right idea. The result<void> expects either an error or a 'void' value. Since void{} isn't a thing in C++, you can do return caf::unit; to tell result<void> to construct an "empty" result. On the receiver's side, you already did the right thing: then with a lambda taking no arguments.
Minor point:
[=](void) { ... }
This is a C-ism from the early days where the compiler allowed you to do silly things. Just drop the void, it serves no purpose. :)

Copying in-memory database to disk crashes on db.close()

Using Qt 5.15.2 on Windows 10 x64
My sqlite3.h says
#define SQLITE_VERSION "3.34.0"
#define SQLITE_VERSION_NUMBER 3034000
#define SQLITE_SOURCE_ID "2020-12-01 16:14:00 a26b6597e3ae272231b96f9982c3bcc17ddec2f2b6eb4df06a224b91089fed5b"
I'm copying a temp database to disk (:memory: if release, tmp file if debug) according to the sqlite3 documentation and other stuff I've found on this site.
How to backup/store between sqlite memory database and file database in Qt?
How to access sqlite3 directly from Qt without linking sqlite3.dll a second time
The problem is if the database is over a given size, the db.close() line crashes the program, showing me this.
The database is created like this. If I use 100 instead of 1000 it seems to work fine, no crash.
void SymbolLibDocument::init() {
if (m_activeState) {
return;
} else {
// Creates temp database to prime save, save as, etc.
// Does not create anything with full filename
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", QString::fromStdString(m_connName));
#if defined(QT_DEBUG)
db.setDatabaseName("tmp_" + QString::fromStdString(m_name));
#elif defined(QT_NO_DEBUG)
db.setDatabaseName(":memory:");
#endif
if(!db.open()) {
qDebug() << "Can't create database";
}
QSqlQuery query(db);
const
QStringList qsl = {"DROP TABLE IF EXISTS hello;",
"CREATE TABLE hello (ID INTEGER PRIMARY KEY AUTOINCREMENT, \n"
" name TEXT CHECK(length(name) > 0)); ",
"INSERT INTO hello (name) VALUES ('giraffe');"};
dbutils::executeList(query, qsl, "Could not init", __LINE__);
query.exec("BEGIN");
QString s = QString("INSERT INTO hello (name) VALUES (:v);");
query.prepare(s);
for (int i = 0; i < 10000; i++) {
query.bindValue(":v", QVariant(i));
query.exec();
}
query.exec("COMMIT");
}
m_activeState = true;
}
void dbSaveFromTo(const std::string & connFrom, const std::string & fileTo) {
// Uses sqlite3 backup mechanism to write database connFrom to fileTo
//auto secs = std::chrono::milliseconds(50);
QString qsConnFrom = QString::fromStdString(connFrom);
// Need to clone db so it can be used from this function, which is callable
// as another thread.
// Also this needs to go in another scope so db object is destroyed
// at exit, prior to close and removal
{
QSqlDatabase db = QSqlDatabase::cloneDatabase(qsConnFrom, "CloneDb");
db.open();
QVariant qvhandle = db.driver()->handle();
if (qvhandle.isValid() && qstrcmp(qvhandle.typeName(), "sqlite3*") == 0) {
sqlite3 *pFrom = *static_cast<sqlite3 **>(qvhandle.data());
sqlite3 *pTo;
sqlite3_open(fileTo.c_str(), &pTo);
sqlite3_backup *pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main");
if (pBackup) {
int pagesPerCycle = 1;
int pageCount = 0;
int pagesCopied = 0;
int pagesRemaining = 0;
do {
(void) sqlite3_backup_step(pBackup, pagesPerCycle);
if (sqlite3_errcode(pFrom) != SQLITE_OK) {
qDebug() << sqlite3_errmsg(pFrom);
}
if (sqlite3_errcode(pTo) != SQLITE_OK) {
qDebug() << sqlite3_errmsg(pTo);
}
pageCount = sqlite3_backup_pagecount(pBackup);
pagesRemaining = sqlite3_backup_remaining(pBackup);
pagesCopied = pageCount - pagesRemaining;
emit intEmitter.emitInt(pagesCopied, pageCount);
qDebug() << "emitting" << pagesCopied << "/" << pageCount;
//std::this_thread::sleep_for(secs);
} while(pagesRemaining > 0);
(void) sqlite3_backup_finish(pBackup);
} else {
throw std::logic_error("sqlite3_backup_init(...) failed");
}
// causes error 21 bad parameter or other API misuse,
// But this occurs even if open and close are called back-to-back
// with no operations in between
sqlite3_close(pTo);
if (sqlite3_errcode(pFrom) != SQLITE_OK) {
qDebug() << sqlite3_errmsg(pFrom);
}
if (sqlite3_errcode(pTo) != SQLITE_OK) {
qDebug() << sqlite3_errmsg(pTo);
}
} else {
throw std::logic_error("invalid driver handle");
}
db.close();
}
QSqlDatabase::removeDatabase("CloneDb");
}
I'm concerned this is caused because I'm using the built-in Qt Sql functionality (QT += sql in .pro file) for db.open, db.close, QSqlDatabase::cloneDatabase, etc.., but I'm using a separate sqlite3 precompiled .dll to access the sqlite3_backup_* functions.
Why does this crash when I do db.close()?
The problem was indeed some weird interaction between the Qt sqlite code used to access the db connection and the sqlite3.dll code used to init and step through the backup.
The solution was to use the Qt Sqlite functionality only to get the filename from the connection name, then use the raw sqlite api to open the source file and do the backup.

Adding a custom sqlite function to a Qt application

I am trying to add a custom sqlite3 regexp function into my Qt application (as recommended by this answer). But as soon as I call the sqlite3_create_function function, I get the message The program has unexpectedly finished. When I debug, it terminates in a segmentation fault in sqlite3_mutex_enter. There is a MWE below, with apologies for the absolute file paths.
The regexp implementation in my code is from this site; it also fails with the msign function here. The various checks of driver()->handle() are straight from the Qt docs.
Incidentally, I used select sqlite_version(); to determine that Qt 5.5 uses sqlite version 3.8.8.2. I found that version by looking through old commits in the Qt GitHub repository.
MWE.pro
QT += core gui
TARGET = MWE
TEMPLATE = app
QT += sql
SOURCES += main.cpp \
D:\Qt\Qt5.5.0\5.5\Src\3rdparty\sqlite\sqlite3.c
HEADERS += D:\Qt\Qt5.5.0\5.5\Src\3rdparty\sqlite\sqlite3.h
main.cpp
#include <QtSql>
#include "D:/Qt/Qt5.5.0/5.5/Src/3rdparty/sqlite/sqlite3.h"
void qtregexp(sqlite3_context* ctx, int argc, sqlite3_value** argv)
{
QRegExp regex;
QString str1((const char*)sqlite3_value_text(argv[0]));
QString str2((const char*)sqlite3_value_text(argv[1]));
regex.setPattern(str1);
regex.setCaseSensitivity(Qt::CaseInsensitive);
bool b = str2.contains(regex);
if (b)
{
sqlite3_result_int(ctx, 1);
}
else
{
sqlite3_result_int(ctx, 0);
}
}
int main(int argc, char *argv[])
{
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("my.db");
db.open();
QVariant v = db.driver()->handle();
if (v.isValid() && qstrcmp(v.typeName(), "sqlite3*")==0) {
sqlite3 *db_handle = *static_cast<sqlite3 **>(v.data());
if (db_handle != 0) { // check that it is not NULL
// This shows that the database handle is generally valid:
qDebug() << sqlite3_db_filename(db_handle, "main");
sqlite3_create_function(db_handle, "regexp", 2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, NULL, &qtregexp, NULL, NULL);
qDebug() << "This won't be reached."
QSqlQuery query;
query.prepare("select regexp('p$','tap');");
query.exec();
query.next();
qDebug() << query.value(0).toString();
}
}
db.close();
}
You need to call sqlite3_initialize() after you get the database handle from Qt, according to this forum post.
...
sqlite3 *db_handle = *static_cast<sqlite3 **>(v.data());
if (db_handle != 0) { // check that it is not NULL
sqlite3_initialize();
...
This solves the issue.

Class objects destruction

In the following code I intentionally set a pointer, p, to NULL after deleting it so that the second object can't delete it again. However, I recieve the following error dialog in Microsoft Visual C++:
Debug Assertion Failed - Expression: _BLOCK_TYPE_IS_VALID(pHead -> nBlockUse)
Full size image of the error dialog.
#include <iostream>
#include <string>
using namespace std;
class line{
public:
line();
~line();
int* p;
};
line::line(){
p = new int;
}
line::~line()
{
if (p != NULL)
{
delete p;
p = NULL;
}
}
int main()
{
line line1,
line2 = line1;
cout << line1.p << endl << line2.p;
cin.get();
return 0;
}
line1 and line2 are both holding a pointer.
You need to understand that line1.p is a separate variable toline2.p, even though they are pointing to the same address in memory.
Suppose that the line2 destructor is invoked first. It will set line2.p to NULL, but this will not alter the location that line1.p is pointing to.
When the line1 destructor is subsequently invoked, it will attempt to deallocate the already deallocated data that line1.p is pointing to, hence the Debug Assertion.

How to use a QFile with std::iostream?

Is it possible to use a QFile like a std::iostream? I'm quite sure there must be a wrapper out there. The question is where?
I have another libs, which requires a std::istream as input parameter, but in my program i only have a QFile at this point.
I came up with my own solution using the following code:
#include <ios>
#include <QIODevice>
class QStdStreamBuf : public std::streambuf
{
public:
QStdStreamBuf(QIODevice *dev) : std::streambuf(), m_dev(dev)
{
// Initialize get pointer. This should be zero so that underflow is called upon first read.
this->setg(0, 0, 0);
}
protected:
virtual std::streamsize xsgetn(std::streambuf::char_type *str, std::streamsize n)
{
return m_dev->read(str, n);
}
virtual std::streamsize xsputn(const std::streambuf::char_type *str, std::streamsize n)
{
return m_dev->write(str, n);
}
virtual std::streambuf::pos_type seekoff(std::streambuf::off_type off, std::ios_base::seekdir dir, std::ios_base::openmode /*__mode*/)
{
switch(dir)
{
case std::ios_base::beg:
break;
case std::ios_base::end:
off = m_dev->size() - off;
break;
case std::ios_base::cur:
off = m_dev->pos() + off;
break;
}
if(m_dev->seek(off))
return m_dev->pos();
else
return std::streambuf::pos_type(std::streambuf::off_type(-1));
}
virtual std::streambuf::pos_type seekpos(std::streambuf::pos_type off, std::ios_base::openmode /*__mode*/)
{
if(m_dev->seek(off))
return m_dev->pos();
else
return std::streambuf::pos_type(std::streambuf::off_type(-1));
}
virtual std::streambuf::int_type underflow()
{
// Read enough bytes to fill the buffer.
std::streamsize len = sgetn(m_inbuf, sizeof(m_inbuf)/sizeof(m_inbuf[0]));
// Since the input buffer content is now valid (or is new)
// the get pointer should be initialized (or reset).
setg(m_inbuf, m_inbuf, m_inbuf + len);
// If nothing was read, then the end is here.
if(len == 0)
return traits_type::eof();
// Return the first character.
return traits_type::not_eof(m_inbuf[0]);
}
private:
static const std::streamsize BUFFER_SIZE = 1024;
std::streambuf::char_type m_inbuf[BUFFER_SIZE];
QIODevice *m_dev;
};
class QStdIStream : public std::istream
{
public:
QStdIStream(QIODevice *dev) : std::istream(m_buf = new QStdStreamBuf(dev)) {}
virtual ~QStdIStream()
{
rdbuf(0);
delete m_buf;
}
private:
QStdStreamBuf * m_buf;
};
I works fine for reading local files. I haven't tested it for writing files. This code is surely not perfect but it works.
I came up with my own solution (which uses the same idea Stephen Chu suggested)
#include <iostream>
#include <fstream>
#include <cstdio>
#include <QtCore>
using namespace std;
void externalLibFunction(istream & input_stream) {
copy(istream_iterator<string>(input_stream),
istream_iterator<string>(),
ostream_iterator<string>(cout, " "));
}
ifstream QFileToifstream(QFile & file) {
Q_ASSERT(file.isReadable());
return ifstream(::_fdopen(file.handle(), "r"));
}
int main(int argc, char ** argv)
{
QFile file("a file");
file.open(QIODevice::WriteOnly);
file.write(QString("some string").toLatin1());
file.close();
file.open(QIODevice::ReadOnly);
std::ifstream ifs(QFileToifstream(file));
externalLibFunction(ifs);
}
Output:
some string
This code uses std::ifstream move constructor (C++x0 feature) specified in 27.9.1.7 basic_ifstream constructors section of Working Draft, Standard for Programming Language C++:
basic_ifstream(basic_ifstream&& rhs);
Effects: Move constructs from the
rvalue rhs. This is accomplished by
move constructing the base class, and
the contained basic_filebuf. Next
basic_istream::set_rdbuf(&sb) is called to install the contained
basic_filebuf.
See How to return an fstream (C++0x) for discussion on this subject.
If the QFile object you get is not open for read already, you can get filename from it and open an ifstream object.
If it's already open, you can get file handle/descriptor with handle() and go from there. There's no portable way of getting a fstream from platform handle. You will have to find a workaround for your platforms.
Here's a good guide for subclassing std::streambuf to provide a non-seekable read-only std::istream: https://stackoverflow.com/a/14086442/316578
Here is a simple class based on that approach which adapts a QFile into an std::streambuf which can then be wrapped in an std::istream.
#include <iostream>
#include <QFile>
constexpr qint64 ERROR = -1;
constexpr qint64 BUFFER_SIZE = 1024;
class QFileInputStreamBuffer final : public std::streambuf {
private:
QFile &m_file;
QByteArray m_buffer;
public:
explicit QFileInputStreamBuffer(QFile &file)
: m_file(file),
m_buffer(BUFFER_SIZE, Qt::Uninitialized) {
}
virtual int underflow() override {
if (atEndOfBuffer()) {
// try to get more data
const qint64 bytesReadIntoBuffer = m_file.read(m_buffer.data(), BUFFER_SIZE);
if (bytesReadIntoBuffer != ERROR) {
setg(m_buffer.data(), m_buffer.data(), m_buffer.data() + bytesReadIntoBuffer);
}
}
if (atEndOfBuffer()) {
// no more data available
return std::char_traits<char>::eof();
}
else {
return std::char_traits<char>::to_int_type(*gptr());
}
}
private:
bool atEndOfBuffer() const {
return gptr() == egptr();
}
};
If you want to be able to more things like seek, write, etc., then you'd need one of the other more complex solutions here which override more streambuf functions.
If you don't care much for performance you can always read everything from the file and dump it into an std::stringstream and then pass that to your library. (or the otherway, buffer everything to a stringstream and then write to a QFile)
Other than that, it doesn't look like the two can inter-operate. At any rate, Qt to STL inter operations are often a cause for obscure bugs and subtle inconsistencies if the version of STL that Qt was compiled with is different in any way from the version of STL you are using. This can happen for instance if you change the version of Visual Studio.

Resources