Files
sqlitebrowser/src/sqlitedb.h
Iulian Onofrei 3cdc65a63f Add automatic crypted databases open via dotenvs (#1404)
* Rename confusing variables

* Fix some project warnings

* Fix code style

* Add constant for the default page size

* Move KeyFormats enum to CipherSettings

* Fix code style

* Fix memory leak

* Stop relying on CipherDialog for encryption settings management

* Fix code style

* Add .env format for QSettings

* Add automatic crypted databases open via dotenvs

This adds support for `.env` files next to the crypted databases that
are to be opened that contains the needed cipher settings.

The only required one is the plain-text password as a value for the key
with the name of the database like this:

    myCryptedDatabase.sqlite = MyPassword

This way, databases with a different extension are supported too:

    myCryptedDatabase.db = MyPassword

You can also specify a custom page size adding a different line
(anywhere in the file) like this:

    myCryptedDatabase.db_pageSize = 2048

If not specified, `1024` is used.

You can also specify the format of the specified key using the
associated integer id:

    anotherCryptedDatabase.sqlite = 0xCAFEBABE
    anotherCryptedDatabase.sqlite_keyFormat = 1

where `1` means a Raw key. If not specified, `0` is used, which means a
simple text Passphrase.

Dotenv files (`.env`) are already used on other platforms and by
different tools to manage environment variables, and it's recommended
to be ignored from version control systems, so they won't leak.

* Add new files to CMakeLists

* Move DotenvFormat include to the implementation

* Fix build error

* Remove superfluous method

(related to ac51c23)

* Remove superfluous checks

* Fix memory leaks

(introduced by 94bbb46)

* Fix code style

* Make dotenv related variable and comment clearer

* Remove duplicated code

* Remove unused forward declaration

(introduced by e5a0293)
2018-07-10 20:46:17 +02:00

224 lines
8.7 KiB
C++

#ifndef SQLITEDB_H
#define SQLITEDB_H
#include "sqlitetypes.h"
#include <memory>
#include <mutex>
#include <condition_variable>
#include <QStringList>
#include <QMultiMap>
#include <QByteArray>
struct sqlite3;
class CipherSettings;
enum
{
kLogMsg_User,
kLogMsg_App
};
typedef QMultiMap<QString, sqlb::ObjectPtr> objectMap; // Maps from object type (table, index, view, trigger) to a pointer to the object representation
typedef QMap<QString, objectMap> schemaMap; // Maps from the schema name (main, temp, attached schemas) to the object map for that schema
int collCompare(void* pArg, int sizeA, const void* sA, int sizeB, const void* sB);
/// represents a single SQLite database. except when noted otherwise,
/// all member functions are to be called from the main UI thread
/// only.
class DBBrowserDB : public QObject
{
Q_OBJECT
private:
/// custom unique_ptr deleter releases database for further use by others
struct DatabaseReleaser
{
DatabaseReleaser(DBBrowserDB * pParent_ = nullptr) : pParent(pParent_) {}
DBBrowserDB * pParent;
void operator() (sqlite3 * db) const
{
if(!db || !pParent)
return;
std::unique_lock<std::mutex> lk(pParent->m);
pParent->db_used = false;
lk.unlock();
pParent->cv.notify_one();
}
};
public:
explicit DBBrowserDB () : _db(nullptr), db_used(false), isEncrypted(false), isReadOnly(false), dontCheckForStructureUpdates(false) {}
virtual ~DBBrowserDB (){}
bool open(const QString& db, bool readOnly = false);
bool attach(const QString& filename, QString attach_as = "");
bool create ( const QString & db);
bool close();
typedef std::unique_ptr<sqlite3, DatabaseReleaser> db_pointer_type;
/**
borrow exclusive address to the currently open database, until
releasing the returned unique_ptr.
the intended use case is that the main UI thread can call this
any time, and then optionally pass the obtained pointer to a
background worker, or release it after doing work immediately.
if database is currently used by somebody else, opens a dialog
box and gives user the opportunity to sqlite3_interrupt() the
operation of the current owner, then tries again.
\param user a string that identifies the new user, and which
can be displayed in the dialog box.
\returns a unique_ptr containing the SQLite database handle, or
nullptr in case no database is open.
**/
db_pointer_type get (QString user);
bool setSavepoint(const QString& pointname = "RESTOREPOINT");
bool releaseSavepoint(const QString& pointname = "RESTOREPOINT");
bool revertToSavepoint(const QString& pointname = "RESTOREPOINT");
bool releaseAllSavepoints();
bool revertAll();
bool dump(const QString& filename, const QStringList& tablesToDump, bool insertColNames, bool insertNew, bool exportSchema, bool exportData, bool keepOldSchema);
bool executeSQL(QString statement, bool dirtyDB = true, bool logsql = true);
bool executeMultiSQL(const QString& statement, bool dirty = true, bool log = false);
const QString& lastError() const { return lastErrorMessage; }
/**
* @brief getRow Executes a sqlite statement to get the rowdata(columns)
* for the given rowid.
* @param schemaName Name of the database schema.
* @param sTableName Table to query.
* @param rowid The rowid to fetch.
* @param rowdata A list of QByteArray containing the row data.
* @return true if statement execution was ok, else false.
*/
bool getRow(const sqlb::ObjectIdentifier& table, const QString& rowid, QVector<QByteArray>& rowdata);
private:
/**
* @brief max Queries the table t for the max value of field.
* @param tableName Table to query
* @param field Field to get the max value
* @return the max value of the field or 0 on error
*/
QString max(const sqlb::ObjectIdentifier& tableName, sqlb::FieldPtr field) const;
public:
void updateSchema();
private:
/**
* @brief Creates an empty insert statement.
* @param schemaName The name of the database schema in which to find the table
* @param pk_value This optional parameter can be used to manually set a specific value for the primary key column
* @return An sqlite conform INSERT INTO statement with empty values. (NULL,'',0)
*/
QString emptyInsertStmt(const QString& schemaName, const sqlb::Table& t, const QString& pk_value = QString()) const;
public:
QString addRecord(const sqlb::ObjectIdentifier& tablename);
bool deleteRecords(const sqlb::ObjectIdentifier& table, const QStringList& rowids, const QString& pseudo_pk = QString());
bool updateRecord(const sqlb::ObjectIdentifier& table, const QString& column, const QString& rowid, const QByteArray& value, bool itsBlob, const QString& pseudo_pk = QString());
bool createTable(const sqlb::ObjectIdentifier& name, const sqlb::FieldVector& structure);
bool renameTable(const QString& schema, const QString& from_table, const QString& to_table);
bool addColumn(const sqlb::ObjectIdentifier& tablename, const sqlb::FieldPtr& field);
/**
* @brief alterTable Can be used to rename, modify or drop an existing column of a given table
* @param schema Specifies the name of the schema, i.e. the database name, of the table
* @param tablename Specifies the name of the table to edit
* @param table Specifies the table to edit. The table constraints are used from this but not the columns
* @param name Name of the column to edit
* @param to The new field definition with changed name, type or the like. If Null-Pointer is given the column is dropped.
* @param move Set this to a value != 0 to move the new column to a different position
* @param newSchema Set this to a non-empty string to move the table to a new schema
* @return true if renaming was successful, false if not. In the latter case also lastErrorMessage is set
*/
bool alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb::Table& table, const QString& name, sqlb::FieldPtr to, int move = 0, QString newSchemaName = QString());
objectMap getBrowsableObjects(const QString& schema) const;
const sqlb::ObjectPtr getObjectByName(const sqlb::ObjectIdentifier& name) const;
bool isOpen() const;
bool encrypted() const { return isEncrypted; }
bool readOnly() const { return isReadOnly; }
bool getDirty() const;
QString currentFile() const { return curDBFilename; }
/// log an SQL statement [thread-safe]
void logSQL(QString statement, int msgtype);
QString getPragma(const QString& pragma);
bool setPragma(const QString& pragma, const QString& value);
bool setPragma(const QString& pragma, const QString& value, QString& originalvalue);
bool setPragma(const QString& pragma, int value, int& originalvalue);
bool loadExtension(const QString& filename);
private:
QVector<QPair<QString, QString>> queryColumnInformation(const QString& schema_name, const QString& object_name);
public:
QString generateSavepointName(const QString& identifier = QString()) const;
// This function generates the name for a temporary table. It guarantees that there is no table with this name yet
QString generateTemporaryTableName(const QString& schema) const;
schemaMap schemata;
signals:
void sqlExecuted(QString sql, int msgtype);
void dbChanged(bool dirty);
void structureUpdated();
void requestCollation(QString name, int eTextRep);
private:
/// external code needs to go through get() to obtain access to the database
sqlite3 * _db;
std::mutex m;
std::condition_variable cv;
bool db_used;
QString db_user;
/// wait for release of the DB locked through a previous get(),
/// giving users the option to discard running task through a
/// message box.
void waitForDbRelease();
QString curDBFilename;
QString lastErrorMessage;
QStringList savepointList;
bool isEncrypted;
bool isReadOnly;
QString primaryKeyForEditing(const sqlb::ObjectIdentifier& table, const QString& pseudo_pk) const;
void collationNeeded(void* pData, sqlite3* db, int eTextRep, const char* sCollationName);
bool tryEncryptionSettings(const QString& filename, bool* encrypted, CipherSettings*& cipherSettings);
bool dontCheckForStructureUpdates;
class NoStructureUpdateChecks
{
public:
explicit NoStructureUpdateChecks(DBBrowserDB& db) : m_db(db) { m_db.dontCheckForStructureUpdates = true; }
~NoStructureUpdateChecks() { m_db.dontCheckForStructureUpdates = false; }
private:
DBBrowserDB& m_db;
};
};
#endif