diff options
Diffstat (limited to 'src/plugins/sqldrivers/mimer/qsql_mimer.cpp')
-rw-r--r-- | src/plugins/sqldrivers/mimer/qsql_mimer.cpp | 1610 |
1 files changed, 1610 insertions, 0 deletions
diff --git a/src/plugins/sqldrivers/mimer/qsql_mimer.cpp b/src/plugins/sqldrivers/mimer/qsql_mimer.cpp new file mode 100644 index 0000000000..7f89e0a0d5 --- /dev/null +++ b/src/plugins/sqldrivers/mimer/qsql_mimer.cpp @@ -0,0 +1,1610 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2022 Mimer Information Technology +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qmetatype.h> +#include <qdatetime.h> +#include <qloggingcategory.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlrecord.h> +#include <qsqlquery.h> +#include <qsocketnotifier.h> +#include <qstringlist.h> +#include <qlocale.h> +#if defined(Q_OS_WIN32) +# include <QtCore/qt_windows.h> +#endif +#include <QtSql/private/qsqlresult_p.h> +#include <QtSql/private/qsqldriver_p.h> +#include "qsql_mimer.h" + +#define MIMER_DEFAULT_DATATYPE 1000 + +Q_DECLARE_OPAQUE_POINTER(MimerSession) +Q_DECLARE_METATYPE(MimerSession) + +Q_DECLARE_OPAQUE_POINTER(MimerStatement) +Q_DECLARE_METATYPE(MimerStatement) + +QT_BEGIN_NAMESPACE + +static Q_LOGGING_CATEGORY(lcMimer, "qt.sql.mimer") + +enum class MimerColumnTypes { + Binary, + Clob, + Blob, + String, + Int, + Numeric, + Long, + Float, + Double, + Boolean, + Uuid, + Date, + Time, + Timestamp, + Unknown +}; + +using namespace Qt::StringLiterals; + +class QMimerSQLResultPrivate; + +class QMimerSQLResult final : public QSqlResult +{ + Q_DECLARE_PRIVATE(QMimerSQLResult) +public: + QMimerSQLResult(const QMimerSQLDriver *db); + virtual ~QMimerSQLResult() override; + QVariant handle() const override; + static constexpr int genericError = -1; + static constexpr int lobChunkMaxSizeSet = 1048500; + static constexpr int lobChunkMaxSizeFetch = 65536; + static constexpr int maxStackStringSize = 200; + static constexpr int maxTimeStringSize = 18; + static constexpr int maxDateStringSize = 10; + static constexpr int maxTimestampStringSize = 29; + +private: + void cleanup(); + bool fetch(int i) override; + bool fetchFirst() override; + bool fetchLast() override; + bool fetchNext() override; + QVariant data(int i) override; + bool isNull(int index) override; + bool reset(const QString &query) override; + int size() override; + int numRowsAffected() override; + QSqlRecord record() const override; + bool prepare(const QString &query) override; + bool execBatch(bool arrayBind = false) override; + bool exec() override; + qint64 currentRow(); + QVariant lastInsertId() const override; +}; + +class QMimerSQLDriverPrivate final : public QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QMimerSQLDriver) +public: + QMimerSQLDriverPrivate() : QSqlDriverPrivate(QSqlDriver::MimerSQL), sessionhandle(nullptr) { } + MimerSession sessionhandle; + QString dbName; + QString dbUser; + void splitTableQualifier(const QString &qualifier, QString *schema, QString *table) const; +}; + +class QMimerSQLResultPrivate : public QSqlResultPrivate +{ + Q_DECLARE_PUBLIC(QMimerSQLResult) +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QMimerSQLDriver) + QMimerSQLResultPrivate(QMimerSQLResult *q, const QMimerSQLDriver *drv) + : QSqlResultPrivate(q, drv), + statementhandle(nullptr), + lobhandle(nullptr), + rowsAffected(0), + preparedQuery(false), + openCursor(false), + openStatement(false), + executedStatement(false), + callWithOut(false), + execBatch(false), + currentRow(QSql::BeforeFirstRow) + { + } + MimerStatement statementhandle; + MimerLob lobhandle; + int rowsAffected; + bool preparedQuery; + bool openCursor; + bool openStatement; + bool executedStatement; + bool callWithOut; + bool execBatch; + qint64 currentSize = -1; + qint64 currentRow; // Only used when forwardOnly() + QVector<QVariant> batch_vector; +}; + +static QSqlError qMakeError(const QString &err, const int errCode, QSqlError::ErrorType type, + const QMimerSQLDriverPrivate *p) +{ + QString msg; + if (p) { + size_t str_len; + int e_code; + int rc; + str_len = (rc = MimerGetError(p->sessionhandle, &e_code, NULL, 0)) + 1; + if (!MIMER_SUCCEEDED(rc)) { + msg = QCoreApplication::translate("QMimerSQL", "No Mimer SQL error for code %1") + .arg(errCode); + } else { + QVarLengthArray<wchar_t> tmp_buff((qsizetype)str_len); + if (!MIMER_SUCCEEDED( + rc = MimerGetError(p->sessionhandle, &e_code, tmp_buff.data(), str_len))) + msg = QCoreApplication::translate("QMimerSQL", "No Mimer SQL error for code %1") + .arg(errCode); + else + msg = QString::fromWCharArray(tmp_buff.data()); + } + } else { + msg = QCoreApplication::translate("QMimerSQL", "Generic Mimer SQL error"); + } + + return QSqlError("QMIMER: "_L1 + err, msg, type, QString::number(errCode)); +} + +static QString msgCouldNotGet(const char *type, int column) +{ + //: Data type, column + return QCoreApplication::translate("QMimerSQLResult", + "Could not get %1, column %2").arg(QLatin1StringView(type)).arg(column); +} + +static QString msgCouldNotSet(const char *type, int column) +{ + //: Data type, parameter + return QCoreApplication::translate("QMimerSQLResult", + "Could not set %1, parameter %2").arg(QLatin1StringView(type)).arg(column); +} + +QMimerSQLDriver::QMimerSQLDriver(QObject *parent) : QSqlDriver(*new QMimerSQLDriverPrivate, parent) +{ +} + +QMimerSQLDriver::QMimerSQLDriver(MimerSession *conn, QObject *parent) + : QSqlDriver(*new QMimerSQLDriverPrivate, parent) +{ + Q_D(QMimerSQLDriver); + if (conn) + d->sessionhandle = *conn; +} + +QMimerSQLDriver::~QMimerSQLDriver() +{ + close(); +} + +QMimerSQLResult::QMimerSQLResult(const QMimerSQLDriver *db) + : QSqlResult(*new QMimerSQLResultPrivate(this, db)) +{ + Q_D(QMimerSQLResult); + d->preparedQuery = db->hasFeature(QSqlDriver::PreparedQueries); +} + +QMimerSQLResult::~QMimerSQLResult() +{ + cleanup(); +} + +static MimerColumnTypes mimerMapColumnTypes(int32_t t) +{ + switch (t) { + case MIMER_BINARY: + case MIMER_BINARY_VARYING: + return MimerColumnTypes::Binary; + case MIMER_BLOB: + case MIMER_NATIVE_BLOB: + return MimerColumnTypes::Blob; + case MIMER_CLOB: + case MIMER_NCLOB: + case MIMER_NATIVE_CLOB: + case MIMER_NATIVE_NCLOB: + return MimerColumnTypes::Clob; + case MIMER_DATE: + return MimerColumnTypes::Date; + case MIMER_TIME: + return MimerColumnTypes::Time; + case MIMER_TIMESTAMP: + return MimerColumnTypes::Timestamp; + case MIMER_INTERVAL_DAY: + case MIMER_INTERVAL_DAY_TO_HOUR: + case MIMER_INTERVAL_DAY_TO_MINUTE: + case MIMER_INTERVAL_DAY_TO_SECOND: + case MIMER_INTERVAL_HOUR: + case MIMER_INTERVAL_HOUR_TO_MINUTE: + case MIMER_INTERVAL_HOUR_TO_SECOND: + case MIMER_INTERVAL_MINUTE: + case MIMER_INTERVAL_MINUTE_TO_SECOND: + case MIMER_INTERVAL_MONTH: + case MIMER_INTERVAL_SECOND: + case MIMER_INTERVAL_YEAR: + case MIMER_INTERVAL_YEAR_TO_MONTH: + case MIMER_NCHAR: + case MIMER_CHARACTER: + case MIMER_CHARACTER_VARYING: + case MIMER_NCHAR_VARYING: + case MIMER_UTF8: + case MIMER_DEFAULT_DATATYPE: + return MimerColumnTypes::String; + case MIMER_INTEGER: + case MIMER_DECIMAL: + case MIMER_FLOAT: + return MimerColumnTypes::Numeric; + case MIMER_BOOLEAN: + return MimerColumnTypes::Boolean; + case MIMER_T_BIGINT: + case MIMER_T_UNSIGNED_BIGINT: + case MIMER_NATIVE_BIGINT_NULLABLE: + case MIMER_NATIVE_BIGINT: + return MimerColumnTypes::Long; + case MIMER_NATIVE_REAL_NULLABLE: + case MIMER_NATIVE_REAL: + case MIMER_T_REAL: + return MimerColumnTypes::Float; + case MIMER_T_FLOAT: + case MIMER_NATIVE_DOUBLE_NULLABLE: + case MIMER_NATIVE_DOUBLE: + case MIMER_T_DOUBLE: + return MimerColumnTypes::Double; + case MIMER_NATIVE_INTEGER: + case MIMER_NATIVE_INTEGER_NULLABLE: + case MIMER_NATIVE_SMALLINT_NULLABLE: + case MIMER_NATIVE_SMALLINT: + case MIMER_T_INTEGER: + case MIMER_T_SMALLINT: + return MimerColumnTypes::Int; + case MIMER_UUID: + return MimerColumnTypes::Uuid; + default: + qCWarning(lcMimer) << "QMimerSQLDriver::mimerMapColumnTypes: Unknown data type:" << t; + } + return MimerColumnTypes::Unknown; +} + +static QMetaType::Type qDecodeMSQLType(int32_t t) +{ + switch (t) { + case MIMER_BINARY: + case MIMER_BINARY_VARYING: + case MIMER_BLOB: + case MIMER_NATIVE_BLOB: + return QMetaType::QByteArray; + case MIMER_CLOB: + case MIMER_NCLOB: + case MIMER_NATIVE_CLOB: + case MIMER_NATIVE_NCLOB: + case MIMER_INTERVAL_DAY: + case MIMER_DECIMAL: + case MIMER_INTERVAL_DAY_TO_HOUR: + case MIMER_INTERVAL_DAY_TO_MINUTE: + case MIMER_INTERVAL_DAY_TO_SECOND: + case MIMER_INTERVAL_HOUR: + case MIMER_INTERVAL_HOUR_TO_MINUTE: + case MIMER_INTERVAL_HOUR_TO_SECOND: + case MIMER_INTERVAL_MINUTE: + case MIMER_INTERVAL_MINUTE_TO_SECOND: + case MIMER_INTERVAL_MONTH: + case MIMER_INTERVAL_SECOND: + case MIMER_INTERVAL_YEAR: + case MIMER_INTERVAL_YEAR_TO_MONTH: + case MIMER_NCHAR: + case MIMER_CHARACTER: + case MIMER_CHARACTER_VARYING: + case MIMER_NCHAR_VARYING: + case MIMER_UTF8: + case MIMER_DEFAULT_DATATYPE: + case MIMER_INTEGER: + case MIMER_FLOAT: + return QMetaType::QString; + case MIMER_BOOLEAN: + return QMetaType::Bool; + case MIMER_T_BIGINT: + case MIMER_T_UNSIGNED_BIGINT: + case MIMER_NATIVE_BIGINT_NULLABLE: + case MIMER_NATIVE_BIGINT: + return QMetaType::LongLong; + case MIMER_NATIVE_REAL_NULLABLE: + case MIMER_NATIVE_REAL: + case MIMER_T_REAL: + return QMetaType::Float; + case MIMER_T_FLOAT: + case MIMER_NATIVE_DOUBLE_NULLABLE: + case MIMER_NATIVE_DOUBLE: + case MIMER_T_DOUBLE: + return QMetaType::Double; + case MIMER_NATIVE_INTEGER_NULLABLE: + case MIMER_T_INTEGER: + case MIMER_NATIVE_INTEGER: + return QMetaType::Int; + case MIMER_NATIVE_SMALLINT_NULLABLE: + case MIMER_T_SMALLINT: + return QMetaType::Int; + case MIMER_DATE: + return QMetaType::QDate; + case MIMER_TIME: + return QMetaType::QTime; + break; + case MIMER_TIMESTAMP: + return QMetaType::QDateTime; + case MIMER_UUID: + return QMetaType::QUuid; + default: + qCWarning(lcMimer) << "QMimerSQLDriver::qDecodeMSQLType: Unknown data type:" << t; + return QMetaType::UnknownType; + } +} + +static int32_t qLookupMimDataType(QStringView s) +{ + if (s == u"BINARY") + return MIMER_BINARY; + if (s == u"BINARY VARYING") + return MIMER_BINARY_VARYING; + if (s == u"BINARY LARGE OBJECT") + return MIMER_BLOB; + if (s == u"CHARACTER LARGE OBJECT") + return MIMER_CLOB; + if (s == u"NATIONAL CHAR LARGE OBJECT") + return MIMER_NCLOB; + if (s == u"INTERVAL DAY") + return MIMER_INTERVAL_DAY; + if (s == u"DECIMAL") + return MIMER_DECIMAL; + if (s == u"INTERVAL DAY TO HOUR") + return MIMER_INTERVAL_DAY_TO_HOUR; + if (s == u"INTERVAL DAY TO MINUTE") + return MIMER_INTERVAL_DAY_TO_MINUTE; + if (s == u"INTERVAL DAY TO SECOND") + return MIMER_INTERVAL_DAY_TO_SECOND; + if (s == u"INTERVAL HOUR") + return MIMER_INTERVAL_HOUR; + if (s == u"INTERVAL HOUR TO MINUTE") + return MIMER_INTERVAL_HOUR_TO_MINUTE; + if (s == u"INTERVAL HOUR TO SECOND") + return MIMER_INTERVAL_HOUR_TO_SECOND; + if (s == u"INTERVAL MINUTE") + return MIMER_INTERVAL_MINUTE; + if (s == u"INTERVAL MINUTE TO SECOND") + return MIMER_INTERVAL_MINUTE_TO_SECOND; + if (s == u"INTERVAL MONTH") + return MIMER_INTERVAL_MONTH; + if (s == u"INTERVAL SECOND") + return MIMER_INTERVAL_SECOND; + if (s == u"INTERVAL YEAR") + return MIMER_INTERVAL_YEAR; + if (s == u"INTERVAL YEAR TO MONTH") + return MIMER_INTERVAL_YEAR_TO_MONTH; + if (s == u"NATIONAL CHARACTER") + return MIMER_NCHAR; + if (s == u"CHARACTER") + return MIMER_CHARACTER; + if (s == u"CHARACTER VARYING") + return MIMER_CHARACTER_VARYING; + if (s == u"NATIONAL CHARACTER VARYING") + return MIMER_NCHAR_VARYING; + if (s == u"UTF-8") + return MIMER_UTF8; + if (s == u"BOOLEAN") + return MIMER_BOOLEAN; + if (s == u"BIGINT") + return MIMER_T_BIGINT; + if (s == u"REAL") + return MIMER_T_REAL; + if (s == u"FLOAT") + return MIMER_T_FLOAT; + if (s == u"DOUBLE PRECISION") + return MIMER_T_DOUBLE; + if (s == u"INTEGER") + return MIMER_T_INTEGER; + if (s == u"SMALLINT") + return MIMER_T_SMALLINT; + if (s == u"DATE") + return MIMER_DATE; + if (s == u"TIME") + return MIMER_TIME; + if (s == u"TIMESTAMP") + return MIMER_TIMESTAMP; + if (s == u"BUILTIN.UUID") + return MIMER_UUID; + if (s == u"USER-DEFINED") + return MIMER_DEFAULT_DATATYPE; + qCWarning(lcMimer) << "QMimerSQLDriver::qLookupMimDataType: Unhandled data type:" << s; + return MIMER_DEFAULT_DATATYPE; +} + +QVariant QMimerSQLResult::handle() const +{ + Q_D(const QMimerSQLResult); + return QVariant::fromValue(d->statementhandle); +} + +void QMimerSQLResult::cleanup() +{ + Q_D(QMimerSQLResult); + if (!driver() || !driver()->isOpen()) { + d->openCursor = false; + d->openStatement = false; + return; + } + if (d->openCursor) { + const int32_t err = MimerCloseCursor(d->statementhandle); + if (!MIMER_SUCCEEDED(err)) + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Could not close cursor"), err, + QSqlError::StatementError, d->drv_d_func())); + d->openCursor = false; + } + if (d->openStatement) { + const int32_t err = MimerEndStatement(&d->statementhandle); + if (!MIMER_SUCCEEDED(err)) + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Could not close statement"), + err, QSqlError::StatementError, d->drv_d_func())); + d->openStatement = false; + } + d->currentSize = -1; +} + +qint64 QMimerSQLResult::currentRow() +{ + Q_D(const QMimerSQLResult); + return d->currentRow; +} + +bool QMimerSQLResult::fetch(int i) +{ + Q_D(const QMimerSQLResult); + int32_t err = 0; + if (!isActive() || !isSelect()) + return false; + if (i == at()) + return true; + if (i < 0) + return false; + + if (isForwardOnly() && i < at()) + return false; + + if (isForwardOnly()) { + bool rc; + do { + rc = fetchNext(); + } while (rc && currentRow() < i); + return rc; + } else { + err = MimerFetchScroll(d->statementhandle, MIMER_ABSOLUTE, i + 1); + if (err == MIMER_NO_DATA) + return false; + } + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(QCoreApplication::translate("QMimerSQLResult", "Fetch did not succeed"), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + setAt(MimerCurrentRow(d->statementhandle) - 1); + return true; +} + +bool QMimerSQLResult::fetchFirst() +{ + Q_D(const QMimerSQLResult); + int32_t err = 0; + if (!isActive() || !isSelect()) + return false; + if (isForwardOnly()) { + if (currentRow() < 0) + return fetchNext(); + else if (currentRow() == 0) + setAt(0); + else + return false; + } else { + err = MimerFetchScroll(d->statementhandle, MIMER_FIRST, 0); + if (MIMER_SUCCEEDED(err) && err != MIMER_NO_DATA) + setAt(0); + } + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Fetch first did not succeed"), err, + QSqlError::StatementError, d->drv_d_func())); + return false; + } + if (err == MIMER_NO_DATA) + return false; + return true; +} + +bool QMimerSQLResult::fetchLast() +{ + Q_D(const QMimerSQLResult); + int32_t err = 0; + int row = 0; + if (!isActive() || !isSelect()) + return false; + if (isForwardOnly()) { + bool rc; + do { + rc = fetchNext(); + } while (rc); + + return currentRow() >= 0; + } else { + err = MimerFetchScroll(d->statementhandle, static_cast<std::int32_t>(MIMER_LAST), 0); + if (err == MIMER_NO_DATA) + return false; + if (MIMER_SUCCEEDED(err)) { + row = MimerCurrentRow(d->statementhandle) - 1; + } else { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult:", "Fetch last did not succeed"), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + } + + if (row < 0) { + setAt(QSql::BeforeFirstRow); + return false; + } else { + setAt(row); + return true; + } +} + +bool QMimerSQLResult::fetchNext() +{ + Q_D(QMimerSQLResult); + int32_t err = 0; + if (!isActive() || !isSelect()) + return false; + if (isForwardOnly()) + err = MimerFetch(d->statementhandle); + else + err = MimerFetchScroll(d->statementhandle, MIMER_NEXT, 0); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Could not fetch next row"), err, + QSqlError::StatementError, d->drv_d_func())); + if (isForwardOnly()) + d->currentRow = QSql::BeforeFirstRow; + return false; + } + if (err == MIMER_NO_DATA) + return false; + if (isForwardOnly()) + setAt(++d->currentRow); + else + setAt(MimerCurrentRow(d->statementhandle) - 1); + return true; +} + +QVariant QMimerSQLResult::data(int i) +{ + Q_D(QMimerSQLResult); + int32_t err; + int32_t mType; + if (d->callWithOut) { + if (i >= MimerParameterCount(d->statementhandle)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult:", "Column %1 out of range") + .arg(i), + genericError, QSqlError::StatementError, nullptr)); + return QVariant(); + } + mType = MimerParameterType(d->statementhandle, static_cast<std::int16_t>(i + 1)); + } else { + if (i >= MimerColumnCount(d->statementhandle)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult:", "Column %1 out of range") + .arg(i), + genericError, QSqlError::StatementError, nullptr)); + return QVariant(); + } + mType = MimerColumnType(d->statementhandle, static_cast<std::int16_t>(i + 1)); + } + const QMetaType::Type type = qDecodeMSQLType(mType); + const MimerColumnTypes mimDataType = mimerMapColumnTypes(mType); + err = MimerIsNull(d->statementhandle, static_cast<std::int16_t>(i + 1)); + if (err > 0) { + return QVariant(QMetaType(type), nullptr); + } else { + switch (mimDataType) { + case MimerColumnTypes::Date: { + wchar_t dateString_w[maxDateStringSize + 1]; + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), dateString_w, + sizeof(dateString_w) / sizeof(dateString_w[0])); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("date", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return QDate::fromString(QString::fromWCharArray(dateString_w), "yyyy-MM-dd"_L1); + } + case MimerColumnTypes::Time: { + wchar_t timeString_w[maxTimeStringSize + 1]; + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), timeString_w, + sizeof(timeString_w) / sizeof(timeString_w[0])); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("time", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + QString timeString = QString::fromWCharArray(timeString_w); + QString timeFormatString = "HH:mm:ss"_L1; + if (timeString.size() > 8) { + timeFormatString.append(".zzz"_L1); + timeString = timeString.left(12); + } + return QTime::fromString(timeString, timeFormatString); + } + case MimerColumnTypes::Timestamp: { + wchar_t dateTimeString_w[maxTimestampStringSize + 1]; + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), + dateTimeString_w, + sizeof(dateTimeString_w) / sizeof(dateTimeString_w[0])); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotGet("date time", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + QString dateTimeString = QString::fromWCharArray(dateTimeString_w); + QString dateTimeFormatString = "yyyy-MM-dd HH:mm:ss"_L1; + if (dateTimeString.size() > 19) { + dateTimeFormatString.append(".zzz"_L1); + dateTimeString = dateTimeString.left(23); + } + return QDateTime::fromString(dateTimeString, dateTimeFormatString); + } + case MimerColumnTypes::Int: { + int resInt; + err = MimerGetInt32(d->statementhandle, static_cast<std::int16_t>(i + 1), &resInt); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("int32", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return resInt; + } + case MimerColumnTypes::Long: { + int64_t resLongLong; + err = MimerGetInt64(d->statementhandle, static_cast<std::int16_t>(i + 1), &resLongLong); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("int64", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return (qlonglong)resLongLong; + } + case MimerColumnTypes::Boolean: { + err = MimerGetBoolean(d->statementhandle, static_cast<std::int16_t>(i + 1)); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotGet("boolean", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return err == 1; + } + case MimerColumnTypes::Float: { + float resFloat; + err = MimerGetFloat(d->statementhandle, static_cast<std::int16_t>(i + 1), &resFloat); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("float", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return resFloat; + } + case MimerColumnTypes::Double: { + double resDouble; + err = MimerGetDouble(d->statementhandle, static_cast<std::int16_t>(i + 1), &resDouble); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotGet("double", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + switch (numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + return static_cast<std::int32_t>(resDouble); + case QSql::LowPrecisionInt64: + return static_cast<qint64>(resDouble); + case QSql::LowPrecisionDouble: + return static_cast<qreal>(resDouble); + case QSql::HighPrecision: + return QString::number(resDouble, 'g', 17); + } + return QVariant(QMetaType(type), nullptr); + } + case MimerColumnTypes::Binary: { + QByteArray byteArray; + // Get size + err = MimerGetBinary(d->statementhandle, static_cast<std::int16_t>(i + 1), NULL, 0); + if (MIMER_SUCCEEDED(err)) { + byteArray.resize(err); + err = MimerGetBinary(d->statementhandle, static_cast<std::int16_t>(i + 1), + byteArray.data(), err); + } + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotGet("binary", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return byteArray; + } + case MimerColumnTypes::Blob: { + QByteArray byteArray; + size_t size; + err = MimerGetLob(d->statementhandle, static_cast<std::int16_t>(i + 1), &size, + &d->lobhandle); + if (MIMER_SUCCEEDED(err)) { + constexpr size_t maxSize = lobChunkMaxSizeFetch; + QVarLengthArray<char> blobchar(lobChunkMaxSizeFetch); + byteArray.reserve(size); + size_t left_to_return = size; + while (left_to_return > 0) { + const size_t bytesToReceive = + left_to_return <= maxSize ? left_to_return : maxSize; + err = MimerGetBlobData(&d->lobhandle, blobchar.data(), bytesToReceive); + byteArray.append(QByteArray::fromRawData(blobchar.data(), bytesToReceive)); + left_to_return -= bytesToReceive; + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("BLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + } + } else { + setLastError(qMakeError(msgCouldNotGet("BLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + return byteArray; + } + case MimerColumnTypes::Numeric: + case MimerColumnTypes::String: { + wchar_t resString_w[maxStackStringSize + 1]; + // Get size + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), resString_w, + 0); + if (MIMER_SUCCEEDED(err)) { + int size = err; + if (err <= maxStackStringSize) { // For smaller strings, use a small buffer for + // efficiency + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), + resString_w, maxStackStringSize + 1); + if (MIMER_SUCCEEDED(err)) + return QString::fromWCharArray(resString_w); + } else { // For larger strings, dynamically allocate memory + QVarLengthArray<wchar_t> largeResString_w(size + 1); + err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), + largeResString_w.data(), size + 1); + if (MIMER_SUCCEEDED(err)) + return QString::fromWCharArray(largeResString_w.data()); + } + } + setLastError(qMakeError(msgCouldNotGet( + mimDataType == MimerColumnTypes::Numeric ? "numeric" : "string", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + case MimerColumnTypes::Clob: { + size_t size; + err = MimerGetLob(d->statementhandle, static_cast<std::int16_t>(i + 1), &size, + &d->lobhandle); + if (MIMER_SUCCEEDED(err)) { + constexpr size_t maxSize = lobChunkMaxSizeFetch; + QVarLengthArray<wchar_t> clobstring_w(lobChunkMaxSizeFetch + 1); + + size_t left_to_return = size; + QString returnString; + while (left_to_return > 0) { + const size_t bytesToReceive = + left_to_return <= maxSize ? left_to_return : maxSize; + err = MimerGetNclobData(&d->lobhandle, clobstring_w.data(), bytesToReceive + 1); + returnString.append(QString::fromWCharArray(clobstring_w.data())); + left_to_return -= bytesToReceive; + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("CLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + } + return returnString; + } + setLastError(qMakeError(msgCouldNotGet("CLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + case MimerColumnTypes::Uuid: { + unsigned char uuidChar[16]; + err = MimerGetUUID(d->statementhandle, static_cast<std::int16_t>(i + 1), uuidChar); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotGet("UUID", i), + err, QSqlError::StatementError, d->drv_d_func())); + return QVariant(QMetaType(type), nullptr); + } + const QByteArray uuidByteArray = QByteArray(reinterpret_cast<char *>(uuidChar), 16); + return QUuid::fromRfc4122(uuidByteArray); + } + case MimerColumnTypes::Unknown: + default: + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Unknown data type %1").arg(i), + genericError, QSqlError::StatementError, nullptr)); + } + return QVariant(QMetaType(type), nullptr); + } +} + +bool QMimerSQLResult::isNull(int index) +{ + Q_D(const QMimerSQLResult); + const int32_t rc = MimerIsNull(d->statementhandle, static_cast<std::int16_t>(index + 1)); + if (!MIMER_SUCCEEDED(rc)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Could not check null, column %1") + .arg(index), + rc, QSqlError::StatementError, d->drv_d_func())); + return false; + } + return rc != 0; +} + +bool QMimerSQLResult::reset(const QString &query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +int QMimerSQLResult::size() +{ + Q_D(QMimerSQLResult); + if (!isActive() || !isSelect() || isForwardOnly()) + return -1; + + if (d->currentSize != -1) + return d->currentSize; + + const int currentRow = MimerCurrentRow(d->statementhandle); + MimerFetchScroll(d->statementhandle, static_cast<std::int32_t>(MIMER_LAST), 0); + int size = MimerCurrentRow(d->statementhandle); + if (!MIMER_SUCCEEDED(size)) + size = -1; + MimerFetchScroll(d->statementhandle, MIMER_ABSOLUTE, currentRow); + d->currentSize = size; + return size; +} + +int QMimerSQLResult::numRowsAffected() +{ + Q_D(const QMimerSQLResult); + return d->rowsAffected; +} + +QSqlRecord QMimerSQLResult::record() const +{ + Q_D(const QMimerSQLResult); + QSqlRecord rec; + if (!isActive() || !isSelect() || !driver()) + return rec; + QSqlField field; + const int colSize = MimerColumnCount(d->statementhandle); + for (int i = 0; i < colSize; i++) { + wchar_t colName_w[100]; + MimerColumnName(d->statementhandle, static_cast<std::int16_t>(i + 1), colName_w, + sizeof(colName_w) / sizeof(colName_w[0])); + field.setName(QString::fromWCharArray(colName_w)); + const int32_t mType = MimerColumnType(d->statementhandle, static_cast<std::int16_t>(i + 1)); + const QMetaType::Type type = qDecodeMSQLType(mType); + field.setMetaType(QMetaType(type)); + field.setValue(QVariant(field.metaType())); + // field.setPrecision(); Should be implemented once the Mimer API can give this + // information. + // field.setLength(); Should be implemented once the Mimer API can give + // this information. + rec.insert(i, field); + } + return rec; +} + +bool QMimerSQLResult::prepare(const QString &query) +{ + Q_D(QMimerSQLResult); + int32_t err; + if (!driver()) + return false; + if (!d->preparedQuery) + return QSqlResult::prepare(query); + if (query.isEmpty()) + return false; + cleanup(); + const int option = isForwardOnly() ? MIMER_FORWARD_ONLY : MIMER_SCROLLABLE; + err = MimerBeginStatement8(d->drv_d_func()->sessionhandle, query.toUtf8().constData(), option, + &d->statementhandle); + if (err == MIMER_STATEMENT_CANNOT_BE_PREPARED) { + err = MimerExecuteStatement8(d->drv_d_func()->sessionhandle, query.toUtf8().constData()); + if (MIMER_SUCCEEDED(err)) { + d->executedStatement = true; + d->openCursor = false; + d->openStatement = false; + return true; + } + } + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(QCoreApplication::translate("QMimerSQLResult", + "Could not prepare/execute statement"), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + d->openStatement = true; + return true; +} + +bool QMimerSQLResult::exec() +{ + Q_D(QMimerSQLResult); + int32_t err; + if (!driver()) + return false; + if (!d->preparedQuery) + return QSqlResult::exec(); + if (d->executedStatement) { + d->executedStatement = false; + return true; + } + if (d->openCursor) { + setAt(QSql::BeforeFirstRow); + err = MimerCloseCursor(d->statementhandle); + d->openCursor = false; + d->currentSize = -1; + } + QVector<QVariant> &values = boundValues(); + if (d->execBatch) + values = d->batch_vector; + int mimParamCount = MimerParameterCount(d->statementhandle); + if (!MIMER_SUCCEEDED(mimParamCount)) + mimParamCount = 0; + if (mimParamCount != values.size()) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Wrong number of parameters"), + genericError, QSqlError::StatementError, nullptr)); + return false; + } + for (int i = 0; i < mimParamCount; i++) { + if (bindValueType(i) == QSql::Out) { + d->callWithOut = true; + continue; + } + const QVariant &val = values.at(i); + if (QSqlResultPrivate::isVariantNull(val) || val.isNull() || val.toString().isNull()) { + err = MimerSetNull(d->statementhandle, i + 1); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("null", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + continue; + } + + const int mimParamType = MimerParameterType(d->statementhandle, i + 1); + const MimerColumnTypes mimDataType = mimerMapColumnTypes(mimParamType); + switch (mimDataType) { + case MimerColumnTypes::Int: { + bool convertOk; + err = MimerSetInt32(d->statementhandle, i + 1, val.toInt(&convertOk)); + if (!convertOk || !MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("int32", i), + convertOk ? err : genericError, QSqlError::StatementError, + convertOk ? d->drv_d_func() : nullptr)); + return false; + } + break; + } + case MimerColumnTypes::Long: { + bool convertOk; + err = MimerSetInt64(d->statementhandle, i + 1, val.toLongLong(&convertOk)); + if (!convertOk || !MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("int64", i), + convertOk ? err : genericError, QSqlError::StatementError, + convertOk ? d->drv_d_func() : nullptr)); + return false; + } + break; + } + case MimerColumnTypes::Float: { + bool convertOk; + err = MimerSetFloat(d->statementhandle, i + 1, val.toFloat(&convertOk)); + if (!convertOk || !MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("float", i), + convertOk ? err : genericError, QSqlError::StatementError, + convertOk ? d->drv_d_func() : nullptr)); + return false; + } + break; + } + case MimerColumnTypes::Double: { + bool convertOk; + err = MimerSetDouble(d->statementhandle, i + 1, val.toDouble(&convertOk)); + if (!convertOk || !MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("double", i), + convertOk ? err : genericError, QSqlError::StatementError, + convertOk ? d->drv_d_func() : nullptr)); + return false; + } + break; + } + case MimerColumnTypes::Binary: { + const QByteArray binArr = val.toByteArray(); + size_t size = static_cast<std::size_t>(binArr.size()); + err = MimerSetBinary(d->statementhandle, i + 1, binArr.data(), size); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("binary", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Boolean: { + err = MimerSetBoolean(d->statementhandle, i + 1, val.toBool() == true ? 1 : 0); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("boolean", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Uuid: { + const QByteArray uuidArray = + QByteArray::fromHex(val.toUuid().toString(QUuid::WithoutBraces).toLatin1()); + const unsigned char *uuid = + reinterpret_cast<const unsigned char *>(uuidArray.constData()); + err = MimerSetUUID(d->statementhandle, i + 1, uuid); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("UUID", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Numeric: + case MimerColumnTypes::String: { + QByteArray string_b = val.toString().trimmed().toUtf8(); + const char *string_u = string_b.constData(); + err = MimerSetString8(d->statementhandle, i + 1, string_u); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet( + mimDataType == MimerColumnTypes::Numeric ? "numeric" : "string", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Date: { + err = MimerSetString8(d->statementhandle, i + 1, val.toString().toUtf8().constData()); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("date", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Time: { + QString timeFormatString = "hh:mm:ss"_L1; + const QTime timeVal = val.toTime(); + if (timeVal.msec() > 0) + timeFormatString.append(".zzz"_L1); + err = MimerSetString8(d->statementhandle, i + 1, + timeVal.toString(timeFormatString).toUtf8().constData()); + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("time", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Timestamp: { + QString dateTimeFormatString = "yyyy-MM-dd hh:mm:ss"_L1; + const QDateTime dateTimeVal = val.toDateTime(); + if (dateTimeVal.time().msec() > 0) + dateTimeFormatString.append(".zzz"_L1); + err = MimerSetString8( + d->statementhandle, i + 1, + val.toDateTime().toString(dateTimeFormatString).toUtf8().constData()); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotSet("datetime", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Blob: { + QByteArray blobArr = val.toByteArray(); + const char *blobData = blobArr.constData(); + qsizetype size = blobArr.size(); + err = MimerSetLob(d->statementhandle, i + 1, size, &d->lobhandle); + if (MIMER_SUCCEEDED(err)) { + qsizetype maxSize = lobChunkMaxSizeSet; + if (size > maxSize) { + qsizetype left_to_send = size; + for (qsizetype k = 0; left_to_send > 0; k++) { + if (left_to_send <= maxSize) { + err = MimerSetBlobData(&d->lobhandle, &blobData[k * maxSize], + left_to_send); + left_to_send = 0; + } else { + err = MimerSetBlobData(&d->lobhandle, &blobData[k * maxSize], maxSize); + left_to_send = left_to_send - maxSize; + } + } + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("BLOB byte array", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + } else { + err = MimerSetBlobData(&d->lobhandle, blobArr, size); + } + } + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotSet("BLOB byte array", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Clob: { + QByteArray string_b = val.toString().trimmed().toUtf8(); + const char *string_u = string_b.constData(); + size_t size_c = 1; + size_t size = 0; + while (string_u[size++]) + if ((string_u[size] & 0xc0) != 0x80) + size_c++; + err = MimerSetLob(d->statementhandle, i + 1, size_c, &d->lobhandle); + if (MIMER_SUCCEEDED(err)) { + constexpr size_t maxSize = lobChunkMaxSizeSet; + if (size > maxSize) { + size_t left_to_send = size; + size_t pos = 0; + uint step_back = 0; + while (left_to_send > 0 && step_back < maxSize) { + step_back = 0; + if (left_to_send <= maxSize) { + err = MimerSetNclobData8(&d->lobhandle, &string_u[pos], left_to_send); + left_to_send = 0; + } else { + // Check that we don't split a multi-byte utf-8 characters + while (pos + maxSize - step_back > 0 + && (string_u[pos + maxSize - step_back] & 0xc0) == 0x80) + step_back++; + err = MimerSetNclobData8(&d->lobhandle, &string_u[pos], + maxSize - step_back); + left_to_send = left_to_send - maxSize + step_back; + pos += maxSize - step_back; + } + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError(msgCouldNotSet("CLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + } + } else { + err = MimerSetNclobData8(&d->lobhandle, string_u, size); + } + } + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(msgCouldNotSet("CLOB", i), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + break; + } + case MimerColumnTypes::Unknown: + default: + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Unknown datatype, parameter %1") + .arg(i), + genericError, QSqlError::StatementError, nullptr)); + return false; + } + } + if (d->execBatch) + return true; + err = MimerExecute(d->statementhandle); + if (MIMER_SUCCEEDED(err)) { + d->rowsAffected = err; + int k = 0; + for (qsizetype i = 0; i < values.size(); i++) { + if (bindValueType(i) == QSql::Out || bindValueType(i) == QSql::InOut) { + bindValue(i, data(k), QSql::In); + k++; + } + } + d->callWithOut = false; + } + setSelect(false); + if (MIMER_SEQUENCE_ERROR == err) { + err = MimerOpenCursor(d->statementhandle); + d->rowsAffected = err; + d->openCursor = true; + d->currentRow = QSql::BeforeFirstRow; + setSelect(true); + } + if (!MIMER_SUCCEEDED(err)) { + setLastError( + qMakeError(QCoreApplication::translate("QMimerSQLResult", + "Could not execute statement/open cursor"), + err, QSqlError::StatementError, d->drv_d_func())); + return false; + } + setActive(true); + return true; +} + +bool QMimerSQLResult::execBatch(bool arrayBind) +{ + Q_D(QMimerSQLResult); + Q_UNUSED(arrayBind); + int32_t err; + const QVector<QVariant> values = boundValues(); + + // Check that we only have input parameters. Currently + // we can only handle batch operations without output parameters. + for (qsizetype i = 0; i < values.first().toList().size(); i++) + if (bindValueType(i) == QSql::Out || bindValueType(i) == QSql::InOut) { + setLastError(qMakeError(QCoreApplication::translate( + "QMimerSQLResult", + "Only input parameters can be used in batch operations"), + genericError, QSqlError::StatementError, nullptr)); + d->execBatch = false; + return false; + } + d->execBatch = true; + for (qsizetype i = 0; i < values.first().toList().size(); i++) { + for (qsizetype j = 0; j < values.size(); j++) + d->batch_vector.append(values.at(j).toList().at(i)); + exec(); + if (i != (values.at(0).toList().size() - 1)) { + err = MimerAddBatch(d->statementhandle); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + //: %1 is the batch number + QCoreApplication::translate("QMimerSQLResult", "Could not add batch %1") + .arg(i), + err, QSqlError::StatementError, d->drv_d_func())); + d->execBatch = false; + return false; + } + } + d->batch_vector.clear(); + } + d->execBatch = false; + err = MimerExecute(d->statementhandle); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLResult", "Could not execute batch"), err, + QSqlError::StatementError, d->drv_d_func())); + return false; + } + return true; +} + +QVariant QMimerSQLResult::lastInsertId() const +{ + Q_D(const QMimerSQLResult); + int64_t lastSequence; + const int32_t err = MimerGetSequenceInt64(d->statementhandle, &lastSequence); + if (!MIMER_SUCCEEDED(err)) + return QVariant(QMetaType(QMetaType::LongLong), nullptr); + return QVariant(qint64(lastSequence)); +} + +bool QMimerSQLDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case NamedPlaceholders: // Is true in reality but Qt parses Sql statement... + case EventNotifications: + case LowPrecisionNumbers: + case MultipleResultSets: + case SimpleLocking: + case CancelQuery: + return false; + case FinishQuery: + case LastInsertId: + case Transactions: + case QuerySize: + case BLOB: + case Unicode: + case PreparedQueries: + case PositionalPlaceholders: + case BatchOperations: + return true; + } + return true; +} + +bool QMimerSQLDriver::open(const QString &db, const QString &user, const QString &password, + const QString &host, int port, const QString &connOpts) +{ + Q_D(QMimerSQLDriver); + Q_UNUSED(host); + Q_UNUSED(port); + Q_UNUSED(connOpts); + if (isOpen()) + close(); + const int32_t err = MimerBeginSession8(db.toUtf8().constData(), user.toUtf8().constData(), + password.toUtf8().constData(), &d->sessionhandle); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLDriver", "Could not connect to database") + + " "_L1 + db, + err, QSqlError::ConnectionError, nullptr)); + setOpenError(true); + return false; + } + d->dbUser = user; + d->dbName = db; + setOpen(true); + setOpenError(false); + return true; +} + +void QMimerSQLDriver::close() +{ + Q_D(QMimerSQLDriver); + if (isOpen()) { + const int end_err = MimerEndSession(&d->sessionhandle); + if (MIMER_SUCCEEDED(end_err)) { + setOpen(false); + setOpenError(false); + } + } +} + +QSqlResult *QMimerSQLDriver::createResult() const +{ + return new QMimerSQLResult(this); +} + +QStringList QMimerSQLDriver::tables(QSql::TableType type) const +{ + QStringList tl; + if (!isOpen()) + return tl; + QSqlQuery t(createResult()); + QString sql; + switch (type) { + case QSql::Tables: { + sql = "select table_name from information_schema.tables where " + "table_type=\'BASE TABLE\' AND table_schema = CURRENT_USER"_L1; + break; + } + case QSql::SystemTables: { + sql = "select table_name from information_schema.tables where " + "table_type=\'BASE TABLE\' AND table_schema = \'SYSTEM\'"_L1; + break; + } + case QSql::Views: { + sql = "select table_name from information_schema.tables where " + "table_type=\'VIEW\' AND table_schema = CURRENT_USER"_L1; + break; + } + case QSql::AllTables: { + sql = "select table_name from information_schema.tables where " + "(table_type=\'VIEW\' or table_type=\'BASE TABLE\')" + " AND (table_schema = CURRENT_USER OR table_schema =\'SYSTEM\')"_L1; + break; + } + default: + break; + } + if (sql.length() > 0) { + t.exec(sql); + while (t.next()) + tl.append(t.value(0).toString()); + } + return tl; +} + +QSqlIndex QMimerSQLDriver::primaryIndex(const QString &tablename) const +{ + Q_D(const QMimerSQLDriver); + if (!isOpen()) + return QSqlIndex(); + QString table = tablename; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + QSqlIndex index(tablename); + QSqlQuery t(createResult()); + QString schema; + QString qualifiedName = table; + d->splitTableQualifier(qualifiedName, &schema, &table); + QString sql = + "select information_schema.ext_access_paths.column_name," + "case when data_type = 'INTERVAL' then 'INTERVAL '|| interval_type " + "when data_type = 'INTEGER' and numeric_precision > 10 then 'BIGINT' " + "when data_type = 'INTEGER' and numeric_precision <= 10 AND NUMERIC_PRECISION > 5 " + "then 'INTEGER' when data_type = 'INTEGER' and numeric_precision <= 5 then 'SMALLINT' " + "else upper(data_type) end as data_type " + "from information_schema.ext_access_paths full outer join " + "information_schema.columns on information_schema.ext_access_paths.column_name = " + "information_schema.columns.column_name and " + "information_schema.ext_access_paths.table_name = " + "information_schema.columns.table_name where " + "information_schema.ext_access_paths.table_name = \'"_L1; + sql.append(table) + .append("\' and index_type = \'PRIMARY KEY\'"_L1); + if (schema.length() == 0) + sql.append(" and table_schema = CURRENT_USER"_L1); + else + sql.append(" and table_schema = \'"_L1).append(schema).append("\'"_L1); + + if (!t.exec(sql)) + return QSqlIndex(); + int i = 0; + while (t.next()) { + QSqlField field(t.value(0).toString(), + QMetaType(qDecodeMSQLType(qLookupMimDataType(t.value(1).toString()))), + tablename); + index.insert(i, field); + index.setName(t.value(0).toString()); + i++; + } + return index; +} + +QSqlRecord QMimerSQLDriver::record(const QString &tablename) const +{ + Q_D(const QMimerSQLDriver); + if (!isOpen()) + return QSqlRecord(); + QSqlRecord rec; + QSqlQuery t(createResult()); + QString qualifiedName = tablename; + if (isIdentifierEscaped(qualifiedName, QSqlDriver::TableName)) + qualifiedName = stripDelimiters(qualifiedName, QSqlDriver::TableName); + QString schema, table; + d->splitTableQualifier(qualifiedName, &schema, &table); + + QString sql = + "select column_name, case when data_type = 'INTERVAL' then 'INTERVAL '|| interval_type " + "when data_type = 'INTEGER' and numeric_precision > 10 then 'BIGINT' " + "when data_type = 'INTEGER' and numeric_precision <= 10 AND numeric_precision > 5 " + "then 'INTEGER' when data_type = 'INTEGER' and numeric_precision <= 5 then 'SMALLINT' " + "else UPPER(data_type) end as data_type, case when is_nullable = 'YES' then false else " + "true end as required, " + "coalesce(numeric_precision, coalesce(datetime_precision,coalesce(interval_precision, " + "-1))) as prec from information_schema.columns where table_name = \'"_L1; + if (schema.length() == 0) + sql.append(table).append("\' and table_schema = CURRENT_USER"_L1); + else + sql.append(table).append("\' and table_schema = \'"_L1).append(schema).append("\'"_L1); + sql.append(" order by ordinal_position"_L1); + if (!t.exec(sql)) + return QSqlRecord(); + + while (t.next()) { + QSqlField field(t.value(0).toString(), + QMetaType(qDecodeMSQLType(qLookupMimDataType(t.value(1).toString()))), + tablename); + field.setRequired(t.value(3).toBool()); + if (t.value(3).toInt() != -1) + field.setPrecision(t.value(3).toInt()); + rec.append(field); + } + + return rec; +} + +QVariant QMimerSQLDriver::handle() const +{ + Q_D(const QMimerSQLDriver); + return QVariant::fromValue(d->sessionhandle); +} + +QString QMimerSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const +{ + Q_UNUSED(type); + QString res = identifier; + if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"')) { + res.replace(u'"', "\"\""_L1); + res = u'"' + res + u'"'; + res.replace(u'.', "\".\""_L1); + } + return res; +} + +bool QMimerSQLDriver::beginTransaction() +{ + Q_D(const QMimerSQLDriver); + const int32_t err = MimerBeginTransaction(d->sessionhandle, MIMER_TRANS_READWRITE); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLDriver", "Could not start transaction"), err, + QSqlError::TransactionError, d)); + return false; + } + return true; +} + +bool QMimerSQLDriver::commitTransaction() +{ + Q_D(const QMimerSQLDriver); + const int32_t err = MimerEndTransaction(d->sessionhandle, MIMER_COMMIT); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLDriver", "Could not commit transaction"), err, + QSqlError::TransactionError, d)); + return false; + } + return true; +} + +bool QMimerSQLDriver::rollbackTransaction() +{ + Q_D(const QMimerSQLDriver); + const int32_t err = MimerEndTransaction(d->sessionhandle, MIMER_ROLLBACK); + if (!MIMER_SUCCEEDED(err)) { + setLastError(qMakeError( + QCoreApplication::translate("QMimerSQLDriver", "Could not roll back transaction"), + err, QSqlError::TransactionError, d)); + return false; + } + return true; +} + +void QMimerSQLDriverPrivate::splitTableQualifier(const QString &qualifiedName, QString *schema, + QString *table) const +{ + const QList<QStringView> l = QStringView(qualifiedName).split(u'.'); + int n = l.count(); + if (n > 2) { + return; // can't possibly be a valid table qualifier + } else if (n == 1) { + *schema = QString(); + *table = l.at(0).toString(); + } else { + *schema = l.at(0).toString(); + *table = l.at(1).toString(); + } +} + +QT_END_NAMESPACE + +#include "moc_qsql_mimer.cpp" |