File indexing completed on 2024-04-28 15:58:53

0001 /* This file is part of the KDE project
0002    Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org>
0003 
0004    This program is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This program is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this program; see the file COPYING.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "SqliteConnection_p.h"
0021 
0022 SqliteConnectionInternal::SqliteConnectionInternal(KDbConnection *connection)
0023         : KDbConnectionInternal(connection)
0024         , data(nullptr)
0025         , data_owned(true)
0026         , m_extensionsLoadingEnabled(false)
0027 {
0028 }
0029 
0030 SqliteConnectionInternal::~SqliteConnectionInternal()
0031 {
0032     if (data_owned && data) {
0033         sqlite3_close(data);
0034         data = nullptr;
0035     }
0036 }
0037 
0038 static const char* const serverResultNames[] = {
0039     "SQLITE_OK", // 0
0040     "SQLITE_ERROR",
0041     "SQLITE_INTERNAL",
0042     "SQLITE_PERM",
0043     "SQLITE_ABORT",
0044     "SQLITE_BUSY",
0045     "SQLITE_LOCKED",
0046     "SQLITE_NOMEM",
0047     "SQLITE_READONLY",
0048     "SQLITE_INTERRUPT",
0049     "SQLITE_IOERR",
0050     "SQLITE_CORRUPT",
0051     "SQLITE_NOTFOUND",
0052     "SQLITE_FULL",
0053     "SQLITE_CANTOPEN",
0054     "SQLITE_PROTOCOL",
0055     "SQLITE_EMPTY",
0056     "SQLITE_SCHEMA",
0057     "SQLITE_TOOBIG",
0058     "SQLITE_CONSTRAINT",
0059     "SQLITE_MISMATCH",
0060     "SQLITE_MISUSE",
0061     "SQLITE_NOLFS",
0062     "SQLITE_AUTH",
0063     "SQLITE_FORMAT",
0064     "SQLITE_RANGE",
0065     "SQLITE_NOTADB", // 26
0066 };
0067 
0068 // static
0069 QString SqliteConnectionInternal::serverResultName(int serverResultCode)
0070 {
0071     if (serverResultCode >= 0 && serverResultCode <= SQLITE_NOTADB)
0072         return QString::fromLatin1(serverResultNames[serverResultCode]);
0073     else if (serverResultCode == SQLITE_ROW)
0074         return QLatin1String("SQLITE_ROW");
0075     else if (serverResultCode == SQLITE_DONE)
0076         return QLatin1String("SQLITE_DONE");
0077     return QString();
0078 }
0079 
0080 void SqliteConnectionInternal::storeResult(KDbResult *result)
0081 {
0082     result->setServerMessage(
0083         (data && result->isError()) ? QString::fromUtf8(sqlite3_errmsg(data))
0084                                     : QString());
0085 }
0086 
0087 bool SqliteConnectionInternal::extensionsLoadingEnabled() const
0088 {
0089     return m_extensionsLoadingEnabled;
0090 }
0091 
0092 void SqliteConnectionInternal::setExtensionsLoadingEnabled(bool set)
0093 {
0094     if (set == m_extensionsLoadingEnabled)
0095         return;
0096     sqlite3_enable_load_extension(data, set);
0097     m_extensionsLoadingEnabled = set;
0098 }
0099 
0100 //static
0101 KDbField::Type SqliteSqlResult::type(int sqliteType)
0102 {
0103     KDbField::Type t;
0104     switch(sqliteType) {
0105     case SQLITE_INTEGER: t = KDbField::Integer; break;
0106     case SQLITE_FLOAT: t = KDbField::Double; break;
0107     case SQLITE_BLOB: t = KDbField::BLOB; break;
0108     case SQLITE_NULL: t = KDbField::Null; break;
0109     case SQLITE_TEXT: t = KDbField::LongText; break;
0110     default: t = KDbField::InvalidType; break;
0111     }
0112     return t;
0113 }
0114 
0115 bool SqliteSqlResult::setConstraints(const QString &tableName, KDbField* field)
0116 {
0117     Q_ASSERT(field);
0118     if (!cacheFieldInfo(tableName)) {
0119         return false;
0120     }
0121     SqliteSqlFieldInfo* info = cachedFieldInfos.value(field->name());
0122     if (info) {
0123         info->setConstraints(field);
0124         return true;
0125     } else {
0126         return false;
0127     }
0128 }
0129 
0130 void SqliteSqlFieldInfo::setConstraints(KDbField* field)
0131 {
0132     field->setDefaultValue(KDbField::convertToType(defaultValue, field->type()));
0133     field->setNotNull(isNotNull);
0134     field->setPrimaryKey(isPrimaryKey);
0135 }
0136 
0137 bool SqliteSqlResult::cacheFieldInfo(const QString &tableName)
0138 {
0139     if (!cachedFieldInfos.isEmpty()) {
0140         return true;
0141     }
0142     QSharedPointer<KDbSqlResult> tableInfoResult = conn->prepareSql(
0143         KDbEscapedString("PRAGMA table_info(%1)").arg(conn->escapeIdentifier(tableName)));
0144     if (!tableInfoResult) {
0145         return false;
0146     }
0147     // Forward-compatible approach: find columns of table_info that we need
0148     const int columns = tableInfoResult->fieldsCount();
0149     enum TableInfoColumns {
0150         TableInfoFieldName,
0151         TableInfoNotNull,
0152         TableInfoDefault,
0153         TableInfoPK
0154     };
0155     QVector<int> columnIndex(TableInfoPK + 1);
0156     int found = 0;
0157     for(int col = 0; col < columns; ++col) {
0158         QScopedPointer<KDbSqlField> f(tableInfoResult->field(col));
0159         if (!f) {
0160             return false;
0161         }
0162         if (f->name() == QLatin1String("name")) {
0163             columnIndex[TableInfoFieldName] = col;
0164             ++found;
0165         } else if (f->name() == QLatin1String("notnull")) {
0166             columnIndex[TableInfoNotNull] = col;
0167             ++found;
0168         } else if (f->name() == QLatin1String("dflt_value")) {
0169             columnIndex[TableInfoDefault] = col;
0170             ++found;
0171         } else if (f->name() == QLatin1String("pk")) {
0172             columnIndex[TableInfoPK] = col;
0173             ++found;
0174         }
0175     }
0176     if (found != TableInfoPK + 1) { // not all columns found
0177         return false;
0178     }
0179 
0180     bool ok = true;
0181     Q_FOREVER {
0182         QSharedPointer<KDbSqlRecord> record = tableInfoResult->fetchRecord();
0183         if (!record) {
0184             ok = !tableInfoResult->lastResult().isError();
0185             break;
0186         }
0187         QScopedPointer<SqliteSqlFieldInfo> info(new SqliteSqlFieldInfo);
0188         const QString name = record->stringValue(columnIndex[TableInfoFieldName]);
0189         if (name.isEmpty()) {
0190             ok = false;
0191             break;
0192         }
0193         info->defaultValue = record->stringValue(columnIndex[TableInfoDefault]);
0194         info->isNotNull
0195             = record->cstringValue(columnIndex[TableInfoNotNull]).rawDataToByteArray() == "1";
0196         //! @todo Support composite primary keys:
0197         //! The "pk" column in the result set is zero for columns that are not part of
0198         //! the primary key, and is the index of the column in the primary key for columns
0199         //! that are part of the primary key.
0200         //! https://www.sqlite.org/pragma.html#pragma_table_info
0201         info->isPrimaryKey
0202             = record->cstringValue(columnIndex[TableInfoPK]).rawDataToByteArray() != "0";
0203         cachedFieldInfos.insert(name, info.take());
0204     }
0205     if (!ok) {
0206         cachedFieldInfos.clear();
0207     }
0208     return ok;
0209 }
0210 
0211 KDbField *SqliteSqlResult::createField(const QString &tableName, int index)
0212 {
0213     QScopedPointer<SqliteSqlField> f(static_cast<SqliteSqlField*>(field(index)));
0214     if (!f) {
0215         return nullptr;
0216     }
0217     const QString caption(f->name());
0218     QString realFieldName(KDb::stringToIdentifier(caption.toLower()));
0219     KDbField *kdbField = new KDbField(realFieldName, type(f->type()));
0220     kdbField->setCaption(caption);
0221     setConstraints(tableName, kdbField);
0222     return kdbField;
0223 }