// 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 #include #include #include #include #include #include #include #include #include #include #include #include #if defined(Q_OS_WIN32) # include #endif #include #include #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 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 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(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(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(i + 1)); } const QMetaType::Type type = qDecodeMSQLType(mType); const MimerColumnTypes mimDataType = mimerMapColumnTypes(mType); err = MimerIsNull(d->statementhandle, static_cast(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(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(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(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(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(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(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(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(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(resDouble); case QSql::LowPrecisionInt64: return static_cast(resDouble); case QSql::LowPrecisionDouble: return static_cast(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(i + 1), NULL, 0); if (MIMER_SUCCEEDED(err)) { byteArray.resize(err); err = MimerGetBinary(d->statementhandle, static_cast(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(i + 1), &size, &d->lobhandle); if (MIMER_SUCCEEDED(err)) { constexpr size_t maxSize = lobChunkMaxSizeFetch; QVarLengthArray 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(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(i + 1), resString_w, maxStackStringSize + 1); if (MIMER_SUCCEEDED(err)) return QString::fromWCharArray(resString_w); } else { // For larger strings, dynamically allocate memory QVarLengthArray largeResString_w(size + 1); err = MimerGetString(d->statementhandle, static_cast(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(i + 1), &size, &d->lobhandle); if (MIMER_SUCCEEDED(err)) { constexpr size_t maxSize = lobChunkMaxSizeFetch; QVarLengthArray 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(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(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(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(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(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(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 &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(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(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 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 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"