/*
Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CamCOPS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/
/* ============================================================================
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtSql module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
============================================================================ */
#define MODIFIED_FROM_SQLITE // shows what we've done
#include "sqlcipherresult.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QMetaType>
#include <QSqlDriver>
#include <QSqlField>
#include "common/preprocessor_aid.h" // IWYU pragma: keep
#include "db/sqlcipherhelpers.h"
#include "db/sqlcipherdriver.h"
#ifdef MODIFIED_FROM_SQLITE
#include "db/whichdb.h"
#ifdef USE_SQLCIPHER
#include <sqlcipher/sqlite3.h>
#endif
#else
#include <sqlite3.h>
#endif
using sqlcipherhelpers::qGetColumnType;
using sqlcipherhelpers::qMakeError;
// ============================================================================
// From combination of SQLiteResultPrivate + SQLiteResult
// ============================================================================
SQLCipherResult::SQLCipherResult(const SQLCipherDriver* drv) :
SqlCachedResult(drv),
m_stmt(nullptr),
m_skipped_status(false),
m_skip_row(false)
{
drv->m_results.append(this);
}
// ============================================================================
// From SQLiteResultPrivate
// ============================================================================
void SQLCipherResult::cleanup()
{
finalize();
m_r_inf.clear();
m_skipped_status = false;
m_skip_row = false;
setAt(QSql::BeforeFirstRow);
setActive(false);
SqlCachedResult::cleanup();
}
void SQLCipherResult::finalize()
{
if (!m_stmt) {
return;
}
sqlite3_finalize(m_stmt);
m_stmt = nullptr;
}
void SQLCipherResult::initColumns(const bool emptyResultset)
{
const int n_cols = sqlite3_column_count(m_stmt);
if (n_cols <= 0) {
return;
}
init(n_cols);
for (int i = 0; i < n_cols; ++i) {
const QString col_name = QString(
reinterpret_cast<const QChar*>(sqlite3_column_name16(m_stmt, i)))
.remove(QLatin1Char('"'));
// must use type_name for resolving the type to match QSqliteDriver::record
QString type_name = QString(reinterpret_cast<const QChar*>(
sqlite3_column_decltype16(m_stmt, i)));
// sqlite3_column_type is documented to have undefined behavior if the result set is empty
int stp = emptyResultset ? -1 : sqlite3_column_type(m_stmt, i);
QMetaType::Type field_type;
if (!type_name.isEmpty()) {
field_type = qGetColumnType(type_name);
} else {
// Get the proper type for the field based on stp value
switch (stp) {
case SQLITE_INTEGER:
field_type = QMetaType::Int;
break;
case SQLITE_FLOAT:
field_type = QMetaType::Double;
break;
case SQLITE_BLOB:
field_type = QMetaType::QByteArray;
break;
case SQLITE_TEXT:
field_type = QMetaType::QString;
break;
case SQLITE_NULL:
default:
field_type = QMetaType::UnknownType;
break;
}
}
QSqlField fld(col_name, QMetaType(field_type));
fld.setSqlType(stp);
m_r_inf.append(fld);
}
}
bool SQLCipherResult::fetchNext(SqlCachedResult::ValueCache& values,
const int idx, const bool initial_fetch)
{
int res;
int i;
if (m_skip_row) {
// already fetched
Q_ASSERT(!initial_fetch);
m_skip_row = false;
for (int i = 0; i < m_first_row.count(); i++) {
values[i] = m_first_row[i];
}
return m_skipped_status;
}
m_skip_row = initial_fetch;
if (initial_fetch) {
m_first_row.clear();
m_first_row.resize(sqlite3_column_count(m_stmt));
}
if (!m_stmt) {
setLastError(QSqlError(
QCoreApplication::translate("SQLCipherResult", "Unable to fetch row"),
QCoreApplication::translate("SQLCipherResult", "No query"),
QSqlError::ConnectionError));
setAt(QSql::AfterLastRow);
return false;
}
res = sqlite3_step(m_stmt);
switch(res) {
case SQLITE_ROW:
// check to see if should fill out columns
if (m_r_inf.isEmpty()) {
// must be first call.
initColumns(false);
}
if (idx < 0 && !initial_fetch) {
return true;
}
for (i = 0; i < m_r_inf.count(); ++i) {
switch (sqlite3_column_type(m_stmt, i)) {
case SQLITE_BLOB:
values[i + idx] = QByteArray(static_cast<const char*>(
sqlite3_column_blob(m_stmt, i)),
sqlite3_column_bytes(m_stmt, i));
break;
case SQLITE_INTEGER:
values[i + idx] = sqlite3_column_int64(m_stmt, i);
break;
case SQLITE_FLOAT:
switch (numericalPrecisionPolicy()) {
case QSql::LowPrecisionInt32:
values[i + idx] = sqlite3_column_int(m_stmt, i);
break;
case QSql::LowPrecisionInt64:
values[i + idx] = sqlite3_column_int64(m_stmt, i);
break;
case QSql::LowPrecisionDouble:
case QSql::HighPrecision:
#ifdef COMPILER_WANTS_DEFAULT_IN_EXHAUSTIVE_SWITCH
default:
#endif
values[i + idx] = sqlite3_column_double(m_stmt, i);
break;
}
break;
case SQLITE_NULL:
values[i + idx] = QVariant(QMetaType(QMetaType::QString));
break;
default:
values[i + idx] = QString(reinterpret_cast<const QChar*>(
sqlite3_column_text16(m_stmt, i)),
sqlite3_column_bytes16(m_stmt, i) / sizeof(QChar));
break;
}
}
return true;
case SQLITE_DONE:
if (m_r_inf.isEmpty()) {
// must be first call.
initColumns(true);
}
setAt(QSql::AfterLastRow);
sqlite3_reset(m_stmt);
return false;
case SQLITE_CONSTRAINT:
case SQLITE_ERROR:
// SQLITE_ERROR is a generic error code and we must call sqlite3_reset()
// to get the specific error message.
res = sqlite3_reset(m_stmt);
setLastError(qMakeError(
cipherDriver()->m_access,
QCoreApplication::translate("SQLCipherResult",
"Unable to fetch row"),
QSqlError::ConnectionError,
res));
setAt(QSql::AfterLastRow);
return false;
case SQLITE_MISUSE:
case SQLITE_BUSY:
default:
// something wrong, don't get col info, but still return false
setLastError(qMakeError(
cipherDriver()->m_access,
QCoreApplication::translate("SQLCipherResult",
"Unable to fetch row"),
QSqlError::ConnectionError,
res));
sqlite3_reset(m_stmt);
setAt(QSql::AfterLastRow);
return false;
}
// will never get here: return false;
}
// ============================================================================
// Extra
// ============================================================================
const SQLCipherDriver* SQLCipherResult::cipherDriver() const
{
const QSqlDriver* drv = driver();
return !drv ? nullptr : reinterpret_cast<const SQLCipherDriver*>(drv);
}
// ============================================================================
// From SQLiteResult
// ============================================================================
SQLCipherResult::~SQLCipherResult()
{
const SQLCipherDriver* cipherdriver = cipherDriver();
if (cipherdriver) {
cipherdriver->m_results.removeOne(this);
}
cleanup();
}
void SQLCipherResult::virtual_hook(int id, void* data)
{
SqlCachedResult::virtual_hook(id, data);
}
bool SQLCipherResult::reset(const QString& query)
{
if (!prepare(query)) {
return false;
}
return exec();
}
bool SQLCipherResult::prepare(const QString& query)
{
if (!driver() || !driver()->isOpen() || driver()->isOpenError()) {
return false;
}
cleanup();
setSelect(false);
const void* pzTail = nullptr;
// Safe to cast to int here as we would not expect the query to be massive
const qsizetype string_size = (query.size() + 1) * static_cast<qsizetype>(sizeof(QChar));
const int num_bytes = static_cast<int>(string_size);
#if (SQLITE_VERSION_NUMBER >= 3003011)
int res = sqlite3_prepare16_v2(
cipherDriver()->m_access,
query.constData(),
num_bytes,
&m_stmt,
&pzTail);
#else
int res = sqlite3_prepare16(
cipherDriver()->m_access,
query.constData(),
num_bytes,
&stmt,
&pzTail);
#endif
if (res != SQLITE_OK) {
setLastError(qMakeError(
cipherDriver()->m_access,
QCoreApplication::translate("SQLCipherResult",
"Unable to execute statement"),
QSqlError::StatementError,
res));
finalize();
return false;
}
if (pzTail && !QString(reinterpret_cast<const QChar*>(pzTail)).trimmed().isEmpty()) {
setLastError(qMakeError(
cipherDriver()->m_access,
QCoreApplication::translate("SQLCipherResult",
"Unable to execute multiple statements at a time"),
QSqlError::StatementError,
SQLITE_MISUSE));
finalize();
return false;
}
return true;
}
bool SQLCipherResult::exec()
{
const QVector<QVariant> values = boundValues();
m_skipped_status = false;
m_skip_row = false;
m_r_inf.clear();
clearValues();
setLastError(QSqlError());
int res = sqlite3_reset(m_stmt);
if (res != SQLITE_OK) {
setLastError(qMakeError(
cipherDriver()->m_access,
QCoreApplication::translate("SQLCipherResult",
"Unable to reset statement"),
QSqlError::StatementError, res));
finalize();
return false;
}
int param_count = sqlite3_bind_parameter_count(m_stmt);
if (param_count == values.count()) {
for (int i = 0; i < param_count; ++i) {
res = SQLITE_OK;
const QVariant& value = values.at(i);
if (value.isNull()) {
res = sqlite3_bind_null(m_stmt, i + 1);
} else {
switch (value.typeId()) {
case QMetaType::QByteArray:
{
auto ba = static_cast<const QByteArray*>(value.constData());
res = sqlite3_bind_blob(m_stmt, i + 1, ba->constData(),
ba->size(), SQLITE_STATIC);
break;
}
case QMetaType::Int:
case QMetaType::Bool:
res = sqlite3_bind_int(m_stmt, i + 1, value.toInt());
break;
case QMetaType::Double:
res = sqlite3_bind_double(m_stmt, i + 1, value.toDouble());
break;
case QMetaType::UInt:
case QMetaType::LongLong:
res = sqlite3_bind_int64(m_stmt, i + 1, value.toLongLong());
break;
case QMetaType::QDateTime:
{
const QDateTime dateTime = value.toDateTime();
const QString str = dateTime.toString(QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz"));
const qsizetype string_size = str.size() * static_cast<qsizetype>(sizeof(ushort));
const int num_bytes = static_cast<int>(string_size);
res = sqlite3_bind_text16(m_stmt, i + 1, str.utf16(),
num_bytes, SQLITE_TRANSIENT);
break;
}
case QMetaType::QTime:
{
const QTime time = value.toTime();
const QString str = time.toString(QStringLiteral("hh:mm:ss.zzz"));
const qsizetype string_size = str.size() * static_cast<qsizetype>(sizeof(ushort));
const int num_bytes = static_cast<int>(string_size);
res = sqlite3_bind_text16(m_stmt, i + 1, str.utf16(),
num_bytes, SQLITE_TRANSIENT);
break;
}
case QMetaType::QString:
{
// lifetime of string == lifetime of its qvariant
auto str = static_cast<const QString*>(value.constData());
const qsizetype string_size = str->size() * static_cast<qsizetype>(sizeof(ushort));
const int num_bytes = static_cast<int>(string_size);
res = sqlite3_bind_text16(m_stmt, i + 1, str->utf16(),
num_bytes, SQLITE_STATIC);
break;
}
default:
{
QString str = value.toString();
// SQLITE_TRANSIENT makes sure that sqlite buffers the data
const qsizetype string_size = (str.size()) * static_cast<qsizetype>(sizeof(QChar));
const int num_bytes = static_cast<int>(string_size);
res = sqlite3_bind_text16(m_stmt, i + 1, str.utf16(),
num_bytes, SQLITE_TRANSIENT);
break;
}
}
}
if (res != SQLITE_OK) {
setLastError(qMakeError(
cipherDriver()->m_access,
QCoreApplication::translate("SQLCipherResult",
"Unable to bind parameters"),
QSqlError::StatementError,
res));
finalize();
return false;
}
}
} else {
setLastError(QSqlError(
QCoreApplication::translate("SQLCipherResult",
"Parameter count mismatch"),
QString(),
QSqlError::StatementError));
return false;
}
m_skipped_status = fetchNext(m_first_row, 0, true);
if (lastError().isValid()) {
setSelect(false);
setActive(false);
return false;
}
setSelect(!m_r_inf.isEmpty());
setActive(true);
return true;
}
bool SQLCipherResult::gotoNext(SqlCachedResult::ValueCache& row, const int idx)
{
return fetchNext(row, idx, false);
}
int SQLCipherResult::size()
{
return -1;
}
int SQLCipherResult::numRowsAffected()
{
return sqlite3_changes(cipherDriver()->m_access);
}
QVariant SQLCipherResult::lastInsertId() const
{
if (isActive()) {
qint64 id = sqlite3_last_insert_rowid(cipherDriver()->m_access);
if (id) {
return id;
}
}
return QVariant();
}
QSqlRecord SQLCipherResult::record() const
{
if (!isActive() || !isSelect()) {
return QSqlRecord();
}
return m_r_inf;
}
void SQLCipherResult::detachFromResultSet()
{
if (m_stmt) {
sqlite3_reset(m_stmt);
}
// RNC:
SqlCachedResult::detachFromResultSet();
}
QVariant SQLCipherResult::handle() const
{
return QVariant::fromValue(reinterpret_cast<void*>(m_stmt));
}