summaryrefslogtreecommitdiffstats
path: root/xbmc/dbwrappers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/dbwrappers
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/dbwrappers')
-rw-r--r--xbmc/dbwrappers/CMakeLists.txt18
-rw-r--r--xbmc/dbwrappers/Database.cpp858
-rw-r--r--xbmc/dbwrappers/Database.h321
-rw-r--r--xbmc/dbwrappers/DatabaseQuery.cpp571
-rw-r--r--xbmc/dbwrappers/DatabaseQuery.h150
-rw-r--r--xbmc/dbwrappers/dataset.cpp625
-rw-r--r--xbmc/dbwrappers/dataset.h470
-rw-r--r--xbmc/dbwrappers/mysqldataset.cpp2099
-rw-r--r--xbmc/dbwrappers/mysqldataset.h167
-rw-r--r--xbmc/dbwrappers/qry_dat.cpp902
-rw-r--r--xbmc/dbwrappers/qry_dat.h265
-rw-r--r--xbmc/dbwrappers/sqlitedataset.cpp1063
-rw-r--r--xbmc/dbwrappers/sqlitedataset.h161
13 files changed, 7670 insertions, 0 deletions
diff --git a/xbmc/dbwrappers/CMakeLists.txt b/xbmc/dbwrappers/CMakeLists.txt
new file mode 100644
index 0000000..16a03f2
--- /dev/null
+++ b/xbmc/dbwrappers/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(SOURCES Database.cpp
+ DatabaseQuery.cpp
+ dataset.cpp
+ qry_dat.cpp
+ sqlitedataset.cpp)
+
+set(HEADERS Database.h
+ DatabaseQuery.h
+ dataset.h
+ qry_dat.h
+ sqlitedataset.h)
+
+if(MYSQLCLIENT_FOUND OR MARIADBCLIENT_FOUND)
+ list(APPEND SOURCES mysqldataset.cpp)
+ list(APPEND HEADERS mysqldataset.h)
+endif()
+
+core_add_library(dbwrappers)
diff --git a/xbmc/dbwrappers/Database.cpp b/xbmc/dbwrappers/Database.cpp
new file mode 100644
index 0000000..1c2c48c
--- /dev/null
+++ b/xbmc/dbwrappers/Database.cpp
@@ -0,0 +1,858 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Database.h"
+
+#include "DatabaseManager.h"
+#include "DbUrl.h"
+#include "ServiceBroker.h"
+#include "filesystem/SpecialProtocol.h"
+#include "profiles/ProfileManager.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "sqlitedataset.h"
+#include "utils/SortUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#if defined(HAS_MYSQL) || defined(HAS_MARIADB)
+#include "mysqldataset.h"
+#endif
+
+#ifdef TARGET_POSIX
+#include "platform/posix/ConvUtils.h"
+#endif
+
+using namespace dbiplus;
+
+#define MAX_COMPRESS_COUNT 20
+
+void CDatabase::Filter::AppendField(const std::string& strField)
+{
+ if (strField.empty())
+ return;
+
+ if (fields.empty() || fields == "*")
+ fields = strField;
+ else
+ fields += ", " + strField;
+}
+
+void CDatabase::Filter::AppendJoin(const std::string& strJoin)
+{
+ if (strJoin.empty())
+ return;
+
+ if (join.empty())
+ join = strJoin;
+ else
+ join += " " + strJoin;
+}
+
+void CDatabase::Filter::AppendWhere(const std::string& strWhere, bool combineWithAnd /* = true */)
+{
+ if (strWhere.empty())
+ return;
+
+ if (where.empty())
+ where = strWhere;
+ else
+ {
+ where = "(" + where + ") ";
+ where += combineWithAnd ? "AND" : "OR";
+ where += " (" + strWhere + ")";
+ }
+}
+
+void CDatabase::Filter::AppendOrder(const std::string& strOrder)
+{
+ if (strOrder.empty())
+ return;
+
+ if (order.empty())
+ order = strOrder;
+ else
+ order += ", " + strOrder;
+}
+
+void CDatabase::Filter::AppendGroup(const std::string& strGroup)
+{
+ if (strGroup.empty())
+ return;
+
+ if (group.empty())
+ group = strGroup;
+ else
+ group += ", " + strGroup;
+}
+
+void CDatabase::ExistsSubQuery::AppendJoin(const std::string& strJoin)
+{
+ if (strJoin.empty())
+ return;
+
+ if (join.empty())
+ join = strJoin;
+ else
+ join += " " + strJoin;
+}
+
+void CDatabase::ExistsSubQuery::AppendWhere(const std::string& strWhere,
+ bool combineWithAnd /* = true */)
+{
+ if (strWhere.empty())
+ return;
+
+ if (where.empty())
+ where = strWhere;
+ else
+ {
+ where += combineWithAnd ? " AND " : " OR ";
+ where += strWhere;
+ }
+}
+
+bool CDatabase::ExistsSubQuery::BuildSQL(std::string& strSQL)
+{
+ if (tablename.empty())
+ return false;
+ strSQL = "EXISTS (SELECT 1 FROM " + tablename;
+ if (!join.empty())
+ strSQL += " " + join;
+ std::string strWhere;
+ if (!param.empty())
+ strWhere = param;
+ if (!where.empty())
+ {
+ if (!strWhere.empty())
+ strWhere += " AND ";
+ strWhere += where;
+ }
+ if (!strWhere.empty())
+ strSQL += " WHERE " + strWhere;
+
+ strSQL += ")";
+ return true;
+}
+
+CDatabase::DatasetLayout::DatasetLayout(size_t totalfields)
+{
+ m_fields.resize(totalfields, DatasetFieldInfo(false, false, -1));
+}
+
+void CDatabase::DatasetLayout::SetField(int fieldNo,
+ const std::string& strField,
+ bool bOutput /*= false*/)
+{
+ if (fieldNo >= 0 && fieldNo < static_cast<int>(m_fields.size()))
+ {
+ m_fields[fieldNo].strField = strField;
+ m_fields[fieldNo].fetch = true;
+ m_fields[fieldNo].output = bOutput;
+ }
+}
+
+void CDatabase::DatasetLayout::AdjustRecordNumbers(int offset)
+{
+ int recno = 0;
+ for (auto& field : m_fields)
+ {
+ if (field.fetch)
+ {
+ field.recno = recno + offset;
+ ++recno;
+ }
+ }
+}
+
+bool CDatabase::DatasetLayout::GetFetch(int fieldno)
+{
+ if (fieldno >= 0 && fieldno < static_cast<int>(m_fields.size()))
+ return m_fields[fieldno].fetch;
+ return false;
+}
+
+void CDatabase::DatasetLayout::SetFetch(int fieldno, bool bFetch /*= true*/)
+{
+ if (fieldno >= 0 && fieldno < static_cast<int>(m_fields.size()))
+ m_fields[fieldno].fetch = bFetch;
+}
+
+bool CDatabase::DatasetLayout::GetOutput(int fieldno)
+{
+ if (fieldno >= 0 && fieldno < static_cast<int>(m_fields.size()))
+ return m_fields[fieldno].output;
+ return false;
+}
+
+int CDatabase::DatasetLayout::GetRecNo(int fieldno)
+{
+ if (fieldno >= 0 && fieldno < static_cast<int>(m_fields.size()))
+ return m_fields[fieldno].recno;
+ return -1;
+}
+
+const std::string CDatabase::DatasetLayout::GetFields()
+{
+ std::string strSQL;
+ for (const auto& field : m_fields)
+ {
+ if (!field.strField.empty() && field.fetch)
+ {
+ if (strSQL.empty())
+ strSQL = field.strField;
+ else
+ strSQL += ", " + field.strField;
+ }
+ }
+
+ return strSQL;
+}
+
+bool CDatabase::DatasetLayout::HasFilterFields()
+{
+ for (const auto& field : m_fields)
+ {
+ if (field.fetch)
+ return true;
+ }
+ return false;
+}
+
+CDatabase::CDatabase()
+ : m_profileManager(*CServiceBroker::GetSettingsComponent()->GetProfileManager())
+{
+ m_openCount = 0;
+ m_sqlite = true;
+ m_multipleExecute = false;
+}
+
+CDatabase::~CDatabase(void)
+{
+ Close();
+}
+
+void CDatabase::Split(const std::string& strFileNameAndPath,
+ std::string& strPath,
+ std::string& strFileName)
+{
+ strFileName = "";
+ strPath = "";
+ int i = strFileNameAndPath.size() - 1;
+ while (i > 0)
+ {
+ char ch = strFileNameAndPath[i];
+ if (ch == ':' || ch == '/' || ch == '\\')
+ break;
+ else
+ i--;
+ }
+ strPath = strFileNameAndPath.substr(0, i);
+ strFileName = strFileNameAndPath.substr(i);
+}
+
+std::string CDatabase::PrepareSQL(std::string strStmt, ...) const
+{
+ std::string strResult = "";
+
+ if (nullptr != m_pDB)
+ {
+ va_list args;
+ va_start(args, strStmt);
+ strResult = m_pDB->vprepare(strStmt.c_str(), args);
+ va_end(args);
+ }
+
+ return strResult;
+}
+
+std::string CDatabase::GetSingleValue(const std::string& query, std::unique_ptr<Dataset>& ds)
+{
+ std::string ret;
+ try
+ {
+ if (!m_pDB || !ds)
+ return ret;
+
+ if (ds->query(query) && ds->num_rows() > 0)
+ ret = ds->fv(0).get_asString();
+
+ ds->close();
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "{} - failed on query '{}'", __FUNCTION__, query);
+ }
+ return ret;
+}
+
+std::string CDatabase::GetSingleValue(const std::string& strTable,
+ const std::string& strColumn,
+ const std::string& strWhereClause /* = std::string() */,
+ const std::string& strOrderBy /* = std::string() */)
+{
+ std::string query = PrepareSQL("SELECT %s FROM %s", strColumn.c_str(), strTable.c_str());
+ if (!strWhereClause.empty())
+ query += " WHERE " + strWhereClause;
+ if (!strOrderBy.empty())
+ query += " ORDER BY " + strOrderBy;
+ query += " LIMIT 1";
+ return GetSingleValue(query, m_pDS);
+}
+
+std::string CDatabase::GetSingleValue(const std::string& query)
+{
+ return GetSingleValue(query, m_pDS);
+}
+
+int CDatabase::GetSingleValueInt(const std::string& query, std::unique_ptr<Dataset>& ds)
+{
+ int ret = 0;
+ try
+ {
+ if (!m_pDB || !ds)
+ return ret;
+
+ if (ds->query(query) && ds->num_rows() > 0)
+ ret = ds->fv(0).get_asInt();
+
+ ds->close();
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "{} - failed on query '{}'", __FUNCTION__, query);
+ }
+ return ret;
+}
+
+int CDatabase::GetSingleValueInt(const std::string& strTable,
+ const std::string& strColumn,
+ const std::string& strWhereClause /* = std::string() */,
+ const std::string& strOrderBy /* = std::string() */)
+{
+ std::string strResult = GetSingleValue(strTable, strColumn, strWhereClause, strOrderBy);
+ return static_cast<int>(strtol(strResult.c_str(), NULL, 10));
+}
+
+int CDatabase::GetSingleValueInt(const std::string& query)
+{
+ return GetSingleValueInt(query, m_pDS);
+}
+
+bool CDatabase::DeleteValues(const std::string& strTable, const Filter& filter /* = Filter() */)
+{
+ std::string strQuery;
+ BuildSQL(PrepareSQL("DELETE FROM %s ", strTable.c_str()), filter, strQuery);
+ return ExecuteQuery(strQuery);
+}
+
+bool CDatabase::BeginMultipleExecute()
+{
+ m_multipleExecute = true;
+ m_multipleQueries.clear();
+ return true;
+}
+
+bool CDatabase::CommitMultipleExecute()
+{
+ m_multipleExecute = false;
+ BeginTransaction();
+ for (const auto& i : m_multipleQueries)
+ {
+ if (!ExecuteQuery(i))
+ {
+ RollbackTransaction();
+ return false;
+ }
+ }
+ m_multipleQueries.clear();
+ return CommitTransaction();
+}
+
+bool CDatabase::ExecuteQuery(const std::string& strQuery)
+{
+ if (m_multipleExecute)
+ {
+ m_multipleQueries.push_back(strQuery);
+ return true;
+ }
+
+ bool bReturn = false;
+
+ try
+ {
+ if (nullptr == m_pDB)
+ return bReturn;
+ if (nullptr == m_pDS)
+ return bReturn;
+ m_pDS->exec(strQuery);
+ bReturn = true;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "{} - failed to execute query '{}'", __FUNCTION__, strQuery);
+ }
+
+ return bReturn;
+}
+
+bool CDatabase::ResultQuery(const std::string& strQuery) const
+{
+ bool bReturn = false;
+
+ try
+ {
+ if (nullptr == m_pDB)
+ return bReturn;
+ if (nullptr == m_pDS)
+ return bReturn;
+
+ std::string strPreparedQuery = PrepareSQL(strQuery);
+
+ bReturn = m_pDS->query(strPreparedQuery);
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "{} - failed to execute query '{}'", __FUNCTION__, strQuery);
+ }
+
+ return bReturn;
+}
+
+bool CDatabase::QueueInsertQuery(const std::string& strQuery)
+{
+ if (strQuery.empty())
+ return false;
+
+ if (!m_bMultiInsert)
+ {
+ if (nullptr == m_pDB)
+ return false;
+ if (nullptr == m_pDS2)
+ return false;
+
+ m_bMultiInsert = true;
+ m_pDS2->insert();
+ }
+
+ m_pDS2->add_insert_sql(strQuery);
+
+ return true;
+}
+
+bool CDatabase::CommitInsertQueries()
+{
+ bool bReturn = true;
+
+ if (m_bMultiInsert)
+ {
+ try
+ {
+ m_bMultiInsert = false;
+ m_pDS2->post();
+ m_pDS2->clear_insert_sql();
+ }
+ catch (...)
+ {
+ bReturn = false;
+ CLog::Log(LOGERROR, "{} - failed to execute queries", __FUNCTION__);
+ }
+ }
+
+ return bReturn;
+}
+
+size_t CDatabase::GetInsertQueriesCount()
+{
+ return m_pDS2->insert_sql_count();
+}
+
+bool CDatabase::QueueDeleteQuery(const std::string& strQuery)
+{
+ if (strQuery.empty() || !m_pDB || !m_pDS)
+ return false;
+
+ m_bMultiDelete = true;
+ m_pDS->del();
+ m_pDS->add_delete_sql(strQuery);
+ return true;
+}
+
+bool CDatabase::CommitDeleteQueries()
+{
+ bool bReturn = true;
+
+ if (m_bMultiDelete)
+ {
+ try
+ {
+ m_bMultiDelete = false;
+ m_pDS->deletion();
+ m_pDS->clear_delete_sql();
+ }
+ catch (...)
+ {
+ bReturn = false;
+ CLog::Log(LOGERROR, "{} - failed to execute queries", __FUNCTION__);
+ }
+ }
+
+ return bReturn;
+}
+
+size_t CDatabase::GetDeleteQueriesCount()
+{
+ return m_pDS->delete_sql_count();
+}
+
+bool CDatabase::Open()
+{
+ DatabaseSettings db_fallback;
+ return Open(db_fallback);
+}
+
+bool CDatabase::Open(const DatabaseSettings& settings)
+{
+ if (IsOpen())
+ {
+ m_openCount++;
+ return true;
+ }
+
+ // check our database manager to see if this database can be opened
+ if (!CServiceBroker::GetDatabaseManager().CanOpen(GetBaseDBName()))
+ return false;
+
+ DatabaseSettings dbSettings = settings;
+ InitSettings(dbSettings);
+
+ std::string dbName = dbSettings.name;
+ dbName += std::to_string(GetSchemaVersion());
+ return Connect(dbName, dbSettings, false);
+}
+
+void CDatabase::InitSettings(DatabaseSettings& dbSettings)
+{
+ m_sqlite = true;
+
+#if defined(HAS_MYSQL) || defined(HAS_MARIADB)
+ if (dbSettings.type == "mysql")
+ {
+ // check we have all information before we cancel the fallback
+ if (!(dbSettings.host.empty() || dbSettings.user.empty() || dbSettings.pass.empty()))
+ m_sqlite = false;
+ else
+ CLog::Log(LOGINFO, "Essential mysql database information is missing. Require at least host, "
+ "user and pass defined.");
+ }
+ else
+#else
+ if (dbSettings.type == "mysql")
+ CLog::Log(
+ LOGERROR,
+ "MySQL library requested but MySQL support is not compiled in. Falling back to sqlite3.");
+#endif
+ {
+ dbSettings.type = "sqlite3";
+ if (dbSettings.host.empty())
+ dbSettings.host = CSpecialProtocol::TranslatePath(m_profileManager.GetDatabaseFolder());
+ }
+
+ // use separate, versioned database
+ if (dbSettings.name.empty())
+ dbSettings.name = GetBaseDBName();
+}
+
+void CDatabase::CopyDB(const std::string& latestDb)
+{
+ m_pDB->copy(latestDb.c_str());
+}
+
+void CDatabase::DropAnalytics()
+{
+ m_pDB->drop_analytics();
+}
+
+bool CDatabase::Connect(const std::string& dbName, const DatabaseSettings& dbSettings, bool create)
+{
+ // create the appropriate database structure
+ if (dbSettings.type == "sqlite3")
+ {
+ m_pDB.reset(new SqliteDatabase());
+ }
+#if defined(HAS_MYSQL) || defined(HAS_MARIADB)
+ else if (dbSettings.type == "mysql")
+ {
+ m_pDB.reset(new MysqlDatabase());
+ }
+#endif
+ else
+ {
+ CLog::Log(LOGERROR, "Unable to determine database type: {}", dbSettings.type);
+ return false;
+ }
+
+ // host name is always required
+ m_pDB->setHostName(dbSettings.host.c_str());
+
+ if (!dbSettings.port.empty())
+ m_pDB->setPort(dbSettings.port.c_str());
+
+ if (!dbSettings.user.empty())
+ m_pDB->setLogin(dbSettings.user.c_str());
+
+ if (!dbSettings.pass.empty())
+ m_pDB->setPasswd(dbSettings.pass.c_str());
+
+ // database name is always required
+ m_pDB->setDatabase(dbName.c_str());
+
+ // set configuration regardless if any are empty
+ m_pDB->setConfig(dbSettings.key.c_str(), dbSettings.cert.c_str(), dbSettings.ca.c_str(),
+ dbSettings.capath.c_str(), dbSettings.ciphers.c_str(), dbSettings.compression);
+
+ // create the datasets
+ m_pDS.reset(m_pDB->CreateDataset());
+ m_pDS2.reset(m_pDB->CreateDataset());
+
+ if (m_pDB->connect(create) != DB_CONNECTION_OK)
+ return false;
+
+ try
+ {
+ // test if db already exists, if not we need to create the tables
+ if (!m_pDB->exists() && create)
+ {
+ if (dbSettings.type == "sqlite3")
+ {
+ // Modern file systems have a cluster/block size of 4k.
+ // To gain better performance when performing write
+ // operations to the database, set the page size of the
+ // database file to 4k.
+ // This needs to be done before any table is created.
+ m_pDS->exec("PRAGMA page_size=4096\n");
+
+ // Also set the memory cache size to 16k
+ m_pDS->exec("PRAGMA default_cache_size=4096\n");
+ }
+ CreateDatabase();
+ }
+
+ // sqlite3 post connection operations
+ if (dbSettings.type == "sqlite3")
+ {
+ m_pDS->exec("PRAGMA cache_size=4096\n");
+ m_pDS->exec("PRAGMA synchronous='NORMAL'\n");
+ m_pDS->exec("PRAGMA count_changes='OFF'\n");
+ }
+ }
+ catch (DbErrors& error)
+ {
+ CLog::Log(LOGERROR, "{} failed with '{}'", __FUNCTION__, error.getMsg());
+ m_openCount = 1; // set to open so we can execute Close()
+ Close();
+ return false;
+ }
+
+ m_openCount = 1; // our database is open
+ return true;
+}
+
+int CDatabase::GetDBVersion()
+{
+ m_pDS->query("SELECT idVersion FROM version\n");
+ if (m_pDS->num_rows() > 0)
+ return m_pDS->fv("idVersion").get_asInt();
+ return 0;
+}
+
+bool CDatabase::IsOpen()
+{
+ return m_openCount > 0;
+}
+
+void CDatabase::Close()
+{
+ if (m_openCount == 0)
+ return;
+
+ if (m_openCount > 1)
+ {
+ m_openCount--;
+ return;
+ }
+
+ m_openCount = 0;
+ m_multipleExecute = false;
+
+ if (nullptr == m_pDB)
+ return;
+ if (nullptr != m_pDS)
+ m_pDS->close();
+ m_pDB->disconnect();
+ m_pDB.reset();
+ m_pDS.reset();
+ m_pDS2.reset();
+}
+
+bool CDatabase::Compress(bool bForce /* =true */)
+{
+ if (!m_sqlite)
+ return true;
+
+ try
+ {
+ if (nullptr == m_pDB)
+ return false;
+ if (nullptr == m_pDS)
+ return false;
+ if (!bForce)
+ {
+ m_pDS->query("select iCompressCount from version");
+ if (!m_pDS->eof())
+ {
+ int iCount = m_pDS->fv(0).get_asInt();
+ if (iCount > MAX_COMPRESS_COUNT)
+ iCount = -1;
+ m_pDS->close();
+ std::string strSQL = PrepareSQL("update version set iCompressCount=%i\n", ++iCount);
+ m_pDS->exec(strSQL);
+ if (iCount != 0)
+ return true;
+ }
+ }
+
+ if (!m_pDS->exec("vacuum\n"))
+ return false;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "{} - Compressing the database failed", __FUNCTION__);
+ return false;
+ }
+ return true;
+}
+
+void CDatabase::Interrupt()
+{
+ m_pDS->interrupt();
+}
+
+void CDatabase::BeginTransaction()
+{
+ try
+ {
+ if (nullptr != m_pDB)
+ m_pDB->start_transaction();
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "database:begintransaction failed");
+ }
+}
+
+bool CDatabase::CommitTransaction()
+{
+ try
+ {
+ if (nullptr != m_pDB)
+ m_pDB->commit_transaction();
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "database:committransaction failed");
+ return false;
+ }
+ return true;
+}
+
+void CDatabase::RollbackTransaction()
+{
+ try
+ {
+ if (nullptr != m_pDB)
+ m_pDB->rollback_transaction();
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "database:rollbacktransaction failed");
+ }
+}
+
+bool CDatabase::CreateDatabase()
+{
+ BeginTransaction();
+ try
+ {
+ CLog::Log(LOGINFO, "creating version table");
+ m_pDS->exec("CREATE TABLE version (idVersion integer, iCompressCount integer)\n");
+ std::string strSQL = PrepareSQL("INSERT INTO version (idVersion,iCompressCount) values(%i,0)\n",
+ GetSchemaVersion());
+ m_pDS->exec(strSQL);
+
+ CreateTables();
+ CreateAnalytics();
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "{} unable to create database:{}", __FUNCTION__, (int)GetLastError());
+ RollbackTransaction();
+ return false;
+ }
+
+ return CommitTransaction();
+}
+
+void CDatabase::UpdateVersionNumber()
+{
+ std::string strSQL = PrepareSQL("UPDATE version SET idVersion=%i\n", GetSchemaVersion());
+ m_pDS->exec(strSQL);
+}
+
+bool CDatabase::BuildSQL(const std::string& strQuery, const Filter& filter, std::string& strSQL)
+{
+ strSQL = strQuery;
+
+ if (!filter.join.empty())
+ strSQL += filter.join;
+ if (!filter.where.empty())
+ strSQL += " WHERE " + filter.where;
+ if (!filter.group.empty())
+ strSQL += " GROUP BY " + filter.group;
+ if (!filter.order.empty())
+ strSQL += " ORDER BY " + filter.order;
+ if (!filter.limit.empty())
+ strSQL += " LIMIT " + filter.limit;
+
+ return true;
+}
+
+bool CDatabase::BuildSQL(const std::string& strBaseDir,
+ const std::string& strQuery,
+ Filter& filter,
+ std::string& strSQL,
+ CDbUrl& dbUrl)
+{
+ SortDescription sorting;
+ return BuildSQL(strBaseDir, strQuery, filter, strSQL, dbUrl, sorting);
+}
+
+bool CDatabase::BuildSQL(const std::string& strBaseDir,
+ const std::string& strQuery,
+ Filter& filter,
+ std::string& strSQL,
+ CDbUrl& dbUrl,
+ SortDescription& sorting /* = SortDescription() */)
+{
+ // parse the base path to get additional filters
+ dbUrl.Reset();
+ if (!dbUrl.FromString(strBaseDir) || !GetFilter(dbUrl, filter, sorting))
+ return false;
+
+ return BuildSQL(strQuery, filter, strSQL);
+}
diff --git a/xbmc/dbwrappers/Database.h b/xbmc/dbwrappers/Database.h
new file mode 100644
index 0000000..2f3d35d
--- /dev/null
+++ b/xbmc/dbwrappers/Database.h
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace dbiplus
+{
+class Database;
+class Dataset;
+} // namespace dbiplus
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class DatabaseSettings; // forward
+class CDbUrl;
+class CProfileManager;
+struct SortDescription;
+
+class CDatabase
+{
+public:
+ class Filter
+ {
+ public:
+ Filter() : fields("*") {}
+ explicit Filter(const char* w) : fields("*"), where(w) {}
+ explicit Filter(const std::string& w) : fields("*"), where(w) {}
+
+ void AppendField(const std::string& strField);
+ void AppendJoin(const std::string& strJoin);
+ void AppendWhere(const std::string& strWhere, bool combineWithAnd = true);
+ void AppendOrder(const std::string& strOrder);
+ void AppendGroup(const std::string& strGroup);
+
+ std::string fields;
+ std::string join;
+ std::string where;
+ std::string order;
+ std::string group;
+ std::string limit;
+ };
+
+ struct DatasetFieldInfo
+ {
+ DatasetFieldInfo(bool fetch, bool output, int recno)
+ : fetch(fetch), output(output), recno(recno)
+ {
+ }
+
+ bool fetch;
+ bool output;
+ int recno;
+ std::string strField;
+ };
+
+ class DatasetLayout
+ {
+ public:
+ DatasetLayout(size_t totalfields);
+ void SetField(int fieldNo, const std::string& strField, bool bOutput = false);
+ void AdjustRecordNumbers(int offset);
+ bool GetFetch(int fieldno);
+ void SetFetch(int fieldno, bool bFetch = true);
+ bool GetOutput(int fieldno);
+ int GetRecNo(int fieldno);
+ const std::string GetFields();
+ bool HasFilterFields();
+
+ private:
+ std::vector<DatasetFieldInfo> m_fields;
+ };
+
+ class ExistsSubQuery
+ {
+ public:
+ explicit ExistsSubQuery(const std::string& table) : tablename(table) {}
+ ExistsSubQuery(const std::string& table, const std::string& parameter)
+ : tablename(table), param(parameter)
+ {
+ }
+ void AppendJoin(const std::string& strJoin);
+ void AppendWhere(const std::string& strWhere, bool combineWithAnd = true);
+ bool BuildSQL(std::string& strSQL);
+
+ std::string tablename;
+ std::string param;
+ std::string join;
+ std::string where;
+ };
+
+ CDatabase();
+ virtual ~CDatabase(void);
+ bool IsOpen();
+ virtual void Close();
+ bool Compress(bool bForce = true);
+ void Interrupt();
+
+ bool Open(const DatabaseSettings& db);
+
+ void BeginTransaction();
+ virtual bool CommitTransaction();
+ void RollbackTransaction();
+ void CopyDB(const std::string& latestDb);
+ void DropAnalytics();
+
+ std::string PrepareSQL(std::string strStmt, ...) const;
+
+ /*!
+ * @brief Get a single value from a table.
+ * @remarks The values of the strWhereClause and strOrderBy parameters have to be FormatSQL'ed when used.
+ * @param strTable The table to get the value from.
+ * @param strColumn The column to get.
+ * @param strWhereClause If set, use this WHERE clause.
+ * @param strOrderBy If set, use this ORDER BY clause.
+ * @return The requested value or an empty string if it wasn't found.
+ */
+ std::string GetSingleValue(const std::string& strTable,
+ const std::string& strColumn,
+ const std::string& strWhereClause = std::string(),
+ const std::string& strOrderBy = std::string());
+ std::string GetSingleValue(const std::string& query);
+
+ /*! \brief Get a single value from a query on a dataset.
+ \param query the query in question.
+ \param ds the dataset to use for the query.
+ \return the value from the query, empty on failure.
+ */
+ std::string GetSingleValue(const std::string& query, std::unique_ptr<dbiplus::Dataset>& ds);
+
+ /*!
+ * @brief Get a single integer value from a table.
+ * @remarks The values of the strWhereClause and strOrderBy parameters have to be FormatSQL'ed when used.
+ * @param strTable The table to get the value from.
+ * @param strColumn The column to get.
+ * @param strWhereClause If set, use this WHERE clause.
+ * @param strOrderBy If set, use this ORDER BY clause.
+ * @return The requested value or 0 if it wasn't found.
+ */
+ int GetSingleValueInt(const std::string& strTable,
+ const std::string& strColumn,
+ const std::string& strWhereClause = std::string(),
+ const std::string& strOrderBy = std::string());
+ int GetSingleValueInt(const std::string& query);
+
+ /*! \brief Get a single integer value from a query on a dataset.
+ \param query the query in question.
+ \param ds the dataset to use for the query.
+ \return the value from the query, 0 on failure.
+ */
+ int GetSingleValueInt(const std::string& query, std::unique_ptr<dbiplus::Dataset>& ds);
+
+ /*!
+ * @brief Delete values from a table.
+ * @param strTable The table to delete the values from.
+ * @param filter The Filter to apply to this query.
+ * @return True if the query was executed successfully, false otherwise.
+ */
+ bool DeleteValues(const std::string& strTable, const Filter& filter = Filter());
+
+ /*!
+ * @brief Execute a query that does not return any result.
+ * Note that if BeginMultipleExecute() has been called, the
+ * query will be queued until CommitMultipleExecute() is called.
+ * @param strQuery The query to execute.
+ * @return True if the query was executed successfully, false otherwise.
+ * @sa BeginMultipleExecute, CommitMultipleExecute
+ */
+ bool ExecuteQuery(const std::string& strQuery);
+
+ /*!
+ * @brief Execute a query that returns a result.
+ * @remarks Call m_pDS->close(); to clean up the dataset when done.
+ * @param strQuery The query to execute.
+ * @return True if the query was executed successfully, false otherwise.
+ */
+ bool ResultQuery(const std::string& strQuery) const;
+
+ /*!
+ * @brief Start a multiple execution queue. Any ExecuteQuery() function
+ * following this call will be queued rather than executed until
+ * CommitMultipleExecute() is performed.
+ * NOTE: Queries that rely on any queued execute query will not
+ * function as expected during this period!
+ * @return true if we could start a multiple execution queue, false otherwise.
+ * @sa CommitMultipleExecute, ExecuteQuery
+ */
+ bool BeginMultipleExecute();
+
+ /*!
+ * @brief Commit the multiple execution queue to the database.
+ * Queries are performed within a transaction, and the transaction
+ * is rolled back should any one query fail.
+ * @return True if the queries were executed successfully, false otherwise.
+ * @sa BeginMultipleExecute, ExecuteQuery
+ */
+ bool CommitMultipleExecute();
+
+ /*!
+ * @brief Put an INSERT or REPLACE query in the queue.
+ * @param strQuery The query to queue.
+ * @return True if the query was added successfully, false otherwise.
+ */
+ bool QueueInsertQuery(const std::string& strQuery);
+
+ /*!
+ * @brief Commit all queries in the queue.
+ * @return True if all queries were executed successfully, false otherwise.
+ */
+ bool CommitInsertQueries();
+
+ /*!
+ * @brief Get the number of INSERT queries in the queue.
+ * @return The number of queries.
+ */
+ size_t GetInsertQueriesCount();
+
+ /*!
+ * @brief Put a DELETE query in the queue.
+ * @param strQuery The query to queue.
+ * @return True if the query was added successfully, false otherwise.
+ */
+ bool QueueDeleteQuery(const std::string& strQuery);
+
+ /*!
+ * @brief Commit all queued DELETE queries.
+ * @return True if all queries were executed successfully, false otherwise.
+ */
+ bool CommitDeleteQueries();
+
+ /*!
+ * @brief Get the number of DELETE queries in the queue.
+ * @return The number of queries.
+ */
+ size_t GetDeleteQueriesCount();
+
+ virtual bool GetFilter(CDbUrl& dbUrl, Filter& filter, SortDescription& sorting) { return true; }
+ virtual bool BuildSQL(const std::string& strBaseDir,
+ const std::string& strQuery,
+ Filter& filter,
+ std::string& strSQL,
+ CDbUrl& dbUrl);
+ virtual bool BuildSQL(const std::string& strBaseDir,
+ const std::string& strQuery,
+ Filter& filter,
+ std::string& strSQL,
+ CDbUrl& dbUrl,
+ SortDescription& sorting);
+
+ bool Connect(const std::string& dbName, const DatabaseSettings& db, bool create);
+
+protected:
+ friend class CDatabaseManager;
+
+ void Split(const std::string& strFileNameAndPath, std::string& strPath, std::string& strFileName);
+
+ virtual bool Open();
+
+ /*! \brief Create database tables and analytics as needed.
+ Calls CreateTables() and CreateAnalytics() on child classes.
+ */
+ bool CreateDatabase();
+
+ /* \brief Create tables for the current database schema.
+ Will be called on database creation.
+ */
+ virtual void CreateTables() = 0;
+
+ /* \brief Create views, indices and triggers for the current database schema.
+ Will be called on database creation and database update.
+ */
+ virtual void CreateAnalytics() = 0;
+
+ /* \brief Update database tables to the current version.
+ Note that analytics (views, indices, triggers) are not present during this
+ function, so don't rely on them.
+ */
+ virtual void UpdateTables(int version) {}
+
+ /* \brief The minimum schema version that we support updating from.
+ */
+ virtual int GetMinSchemaVersion() const { return 0; }
+
+ /* \brief The current schema version.
+ */
+ virtual int GetSchemaVersion() const = 0;
+ virtual const char* GetBaseDBName() const = 0;
+
+ int GetDBVersion();
+
+ bool BuildSQL(const std::string& strQuery, const Filter& filter, std::string& strSQL);
+
+ bool m_sqlite; ///< \brief whether we use sqlite (defaults to true)
+
+ std::unique_ptr<dbiplus::Database> m_pDB;
+ std::unique_ptr<dbiplus::Dataset> m_pDS;
+ std::unique_ptr<dbiplus::Dataset> m_pDS2;
+
+protected:
+ // Construction parameters
+ const CProfileManager& m_profileManager;
+
+private:
+ void InitSettings(DatabaseSettings& dbSettings);
+ void UpdateVersionNumber();
+
+ bool m_bMultiInsert =
+ false; /*!< True if there are any queries in the insert queue, false otherwise */
+ bool m_bMultiDelete =
+ false; /*!< True if there are any queries in the delete queue, false otherwise */
+ unsigned int m_openCount;
+
+ bool m_multipleExecute;
+ std::vector<std::string> m_multipleQueries;
+};
diff --git a/xbmc/dbwrappers/DatabaseQuery.cpp b/xbmc/dbwrappers/DatabaseQuery.cpp
new file mode 100644
index 0000000..c1432be
--- /dev/null
+++ b/xbmc/dbwrappers/DatabaseQuery.cpp
@@ -0,0 +1,571 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DatabaseQuery.h"
+
+#include "Database.h"
+#include "XBDateTime.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+#include "utils/XBMCTinyXML.h"
+
+typedef struct
+{
+ char string[15];
+ CDatabaseQueryRule::SEARCH_OPERATOR op;
+ int localizedString;
+} operatorField;
+
+static const operatorField operators[] = {
+ {"contains", CDatabaseQueryRule::OPERATOR_CONTAINS, 21400},
+ {"doesnotcontain", CDatabaseQueryRule::OPERATOR_DOES_NOT_CONTAIN, 21401},
+ {"is", CDatabaseQueryRule::OPERATOR_EQUALS, 21402},
+ {"isnot", CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL, 21403},
+ {"startswith", CDatabaseQueryRule::OPERATOR_STARTS_WITH, 21404},
+ {"endswith", CDatabaseQueryRule::OPERATOR_ENDS_WITH, 21405},
+ {"greaterthan", CDatabaseQueryRule::OPERATOR_GREATER_THAN, 21406},
+ {"lessthan", CDatabaseQueryRule::OPERATOR_LESS_THAN, 21407},
+ {"after", CDatabaseQueryRule::OPERATOR_AFTER, 21408},
+ {"before", CDatabaseQueryRule::OPERATOR_BEFORE, 21409},
+ {"inthelast", CDatabaseQueryRule::OPERATOR_IN_THE_LAST, 21410},
+ {"notinthelast", CDatabaseQueryRule::OPERATOR_NOT_IN_THE_LAST, 21411},
+ {"true", CDatabaseQueryRule::OPERATOR_TRUE, 20122},
+ {"false", CDatabaseQueryRule::OPERATOR_FALSE, 20424},
+ {"between", CDatabaseQueryRule::OPERATOR_BETWEEN, 21456}};
+
+CDatabaseQueryRule::CDatabaseQueryRule()
+{
+ m_field = 0;
+ m_operator = OPERATOR_CONTAINS;
+}
+
+bool CDatabaseQueryRule::Load(const TiXmlNode* node, const std::string& encoding /* = "UTF-8" */)
+{
+ if (node == NULL)
+ return false;
+
+ const TiXmlElement* element = node->ToElement();
+ if (element == NULL)
+ return false;
+
+ // format is:
+ // <rule field="Genre" operator="contains">parameter</rule>
+ // where parameter can either be a string or a list of
+ // <value> tags containing a string
+ const char* field = element->Attribute("field");
+ const char* oper = element->Attribute("operator");
+ if (field == NULL || oper == NULL)
+ return false;
+
+ m_field = TranslateField(field);
+ m_operator = TranslateOperator(oper);
+
+ if (m_operator == OPERATOR_TRUE || m_operator == OPERATOR_FALSE)
+ return true;
+
+ const TiXmlNode* parameter = element->FirstChild();
+ if (parameter == NULL)
+ return false;
+
+ if (parameter->Type() == TiXmlNode::TINYXML_TEXT)
+ {
+ std::string utf8Parameter;
+ if (encoding.empty()) // utf8
+ utf8Parameter = parameter->ValueStr();
+ else
+ g_charsetConverter.ToUtf8(encoding, parameter->ValueStr(), utf8Parameter);
+
+ if (!utf8Parameter.empty())
+ m_parameter.push_back(utf8Parameter);
+ }
+ else if (parameter->Type() == TiXmlNode::TINYXML_ELEMENT)
+ {
+ const TiXmlNode* valueNode = element->FirstChild("value");
+ while (valueNode != NULL)
+ {
+ const TiXmlNode* value = valueNode->FirstChild();
+ if (value != NULL && value->Type() == TiXmlNode::TINYXML_TEXT)
+ {
+ std::string utf8Parameter;
+ if (encoding.empty()) // utf8
+ utf8Parameter = value->ValueStr();
+ else
+ g_charsetConverter.ToUtf8(encoding, value->ValueStr(), utf8Parameter);
+
+ if (!utf8Parameter.empty())
+ m_parameter.push_back(utf8Parameter);
+ }
+
+ valueNode = valueNode->NextSibling("value");
+ }
+ }
+ else
+ return false;
+
+ return true;
+}
+
+bool CDatabaseQueryRule::Load(const CVariant& obj)
+{
+ if (!obj.isMember("field") || !obj["field"].isString() || !obj.isMember("operator") ||
+ !obj["operator"].isString())
+ return false;
+
+ m_field = TranslateField(obj["field"].asString().c_str());
+ m_operator = TranslateOperator(obj["operator"].asString().c_str());
+
+ if (m_operator == OPERATOR_TRUE || m_operator == OPERATOR_FALSE)
+ return true;
+
+ if (!obj.isMember("value") || (!obj["value"].isString() && !obj["value"].isArray()))
+ return false;
+
+ const CVariant& value = obj["value"];
+ if (value.isString())
+ m_parameter.push_back(value.asString());
+ else if (value.isArray())
+ {
+ for (CVariant::const_iterator_array val = value.begin_array(); val != value.end_array(); ++val)
+ {
+ if (val->isString() && !val->asString().empty())
+ m_parameter.push_back(val->asString());
+ }
+ if (m_parameter.empty())
+ m_parameter.emplace_back("");
+ }
+ else
+ return false;
+
+ return true;
+}
+
+bool CDatabaseQueryRule::Save(TiXmlNode* parent) const
+{
+ if (parent == NULL ||
+ (m_parameter.empty() && m_operator != OPERATOR_TRUE && m_operator != OPERATOR_FALSE))
+ return false;
+
+ TiXmlElement rule("rule");
+ rule.SetAttribute("field", TranslateField(m_field).c_str());
+ rule.SetAttribute("operator", TranslateOperator(m_operator).c_str());
+
+ for (const auto& it : m_parameter)
+ {
+ TiXmlElement value("value");
+ TiXmlText text(it);
+ value.InsertEndChild(text);
+ rule.InsertEndChild(value);
+ }
+
+ parent->InsertEndChild(rule);
+
+ return true;
+}
+
+bool CDatabaseQueryRule::Save(CVariant& obj) const
+{
+ if (obj.isNull() ||
+ (m_parameter.empty() && m_operator != OPERATOR_TRUE && m_operator != OPERATOR_FALSE))
+ return false;
+
+ obj["field"] = TranslateField(m_field);
+ obj["operator"] = TranslateOperator(m_operator);
+ obj["value"] = m_parameter;
+
+ return true;
+}
+
+CDatabaseQueryRule::SEARCH_OPERATOR CDatabaseQueryRule::TranslateOperator(const char* oper)
+{
+ for (const operatorField& o : operators)
+ if (StringUtils::EqualsNoCase(oper, o.string))
+ return o.op;
+ return OPERATOR_CONTAINS;
+}
+
+std::string CDatabaseQueryRule::TranslateOperator(SEARCH_OPERATOR oper)
+{
+ for (const operatorField& o : operators)
+ if (oper == o.op)
+ return o.string;
+ return "contains";
+}
+
+std::string CDatabaseQueryRule::GetLocalizedOperator(SEARCH_OPERATOR oper)
+{
+ for (const operatorField& o : operators)
+ if (oper == o.op)
+ return g_localizeStrings.Get(o.localizedString);
+ return g_localizeStrings.Get(16018);
+}
+
+void CDatabaseQueryRule::GetAvailableOperators(std::vector<std::string>& operatorList)
+{
+ for (const operatorField& o : operators)
+ operatorList.emplace_back(o.string);
+}
+
+std::string CDatabaseQueryRule::GetParameter() const
+{
+ return StringUtils::Join(m_parameter, DATABASEQUERY_RULE_VALUE_SEPARATOR);
+}
+
+void CDatabaseQueryRule::SetParameter(const std::string& value)
+{
+ m_parameter = StringUtils::Split(value, DATABASEQUERY_RULE_VALUE_SEPARATOR);
+}
+
+void CDatabaseQueryRule::SetParameter(const std::vector<std::string>& values)
+{
+ m_parameter.assign(values.begin(), values.end());
+}
+
+std::string CDatabaseQueryRule::ValidateParameter(const std::string& parameter) const
+{
+ if ((GetFieldType(m_field) == REAL_FIELD || GetFieldType(m_field) == NUMERIC_FIELD ||
+ GetFieldType(m_field) == SECONDS_FIELD) &&
+ parameter.empty())
+ return "0"; // interpret empty fields as 0
+ return parameter;
+}
+
+std::string CDatabaseQueryRule::FormatParameter(const std::string& operatorString,
+ const std::string& param,
+ const CDatabase& db,
+ const std::string& strType) const
+{
+ std::string parameter;
+ if (GetFieldType(m_field) == TEXTIN_FIELD)
+ {
+ std::vector<std::string> split = StringUtils::Split(param, ',');
+ for (std::string& itIn : split)
+ {
+ if (!parameter.empty())
+ parameter += ",";
+ parameter += db.PrepareSQL("'%s'", StringUtils::Trim(itIn).c_str());
+ }
+ parameter = " IN (" + parameter + ")";
+ }
+ else
+ parameter = db.PrepareSQL(operatorString, ValidateParameter(param).c_str());
+
+ if (GetFieldType(m_field) == DATE_FIELD)
+ {
+ if (m_operator == OPERATOR_IN_THE_LAST || m_operator == OPERATOR_NOT_IN_THE_LAST)
+ { // translate time period
+ CDateTime date = CDateTime::GetCurrentDateTime();
+ CDateTimeSpan span;
+ span.SetFromPeriod(param);
+ date -= span;
+ parameter = db.PrepareSQL(operatorString, date.GetAsDBDate().c_str());
+ }
+ }
+ return parameter;
+}
+
+std::string CDatabaseQueryRule::GetOperatorString(SEARCH_OPERATOR op) const
+{
+ std::string operatorString;
+ if (GetFieldType(m_field) != TEXTIN_FIELD)
+ {
+ // the comparison piece
+ switch (op)
+ {
+ case OPERATOR_CONTAINS:
+ operatorString = " LIKE '%%%s%%'";
+ break;
+ case OPERATOR_DOES_NOT_CONTAIN:
+ operatorString = " LIKE '%%%s%%'";
+ break;
+ case OPERATOR_EQUALS:
+ if (GetFieldType(m_field) == REAL_FIELD || GetFieldType(m_field) == NUMERIC_FIELD ||
+ GetFieldType(m_field) == SECONDS_FIELD)
+ operatorString = " = %s";
+ else
+ operatorString = " LIKE '%s'";
+ break;
+ case OPERATOR_DOES_NOT_EQUAL:
+ if (GetFieldType(m_field) == REAL_FIELD || GetFieldType(m_field) == NUMERIC_FIELD ||
+ GetFieldType(m_field) == SECONDS_FIELD)
+ operatorString = " != %s";
+ else
+ operatorString = " LIKE '%s'";
+ break;
+ case OPERATOR_STARTS_WITH:
+ operatorString = " LIKE '%s%%'";
+ break;
+ case OPERATOR_ENDS_WITH:
+ operatorString = " LIKE '%%%s'";
+ break;
+ case OPERATOR_AFTER:
+ case OPERATOR_GREATER_THAN:
+ case OPERATOR_IN_THE_LAST:
+ operatorString = " > ";
+ if (GetFieldType(m_field) == REAL_FIELD || GetFieldType(m_field) == NUMERIC_FIELD ||
+ GetFieldType(m_field) == SECONDS_FIELD)
+ operatorString += "%s";
+ else
+ operatorString += "'%s'";
+ break;
+ case OPERATOR_BEFORE:
+ case OPERATOR_LESS_THAN:
+ case OPERATOR_NOT_IN_THE_LAST:
+ operatorString = " < ";
+ if (GetFieldType(m_field) == REAL_FIELD || GetFieldType(m_field) == NUMERIC_FIELD ||
+ GetFieldType(m_field) == SECONDS_FIELD)
+ operatorString += "%s";
+ else
+ operatorString += "'%s'";
+ break;
+ case OPERATOR_TRUE:
+ operatorString = " = 1";
+ break;
+ case OPERATOR_FALSE:
+ operatorString = " = 0";
+ break;
+ default:
+ break;
+ }
+ }
+ return operatorString;
+}
+
+std::string CDatabaseQueryRule::GetWhereClause(const CDatabase& db,
+ const std::string& strType) const
+{
+ SEARCH_OPERATOR op = GetOperator(strType);
+
+ std::string operatorString = GetOperatorString(op);
+ std::string negate;
+ if (op == OPERATOR_DOES_NOT_CONTAIN || op == OPERATOR_FALSE ||
+ (op == OPERATOR_DOES_NOT_EQUAL && GetFieldType(m_field) != REAL_FIELD &&
+ GetFieldType(m_field) != NUMERIC_FIELD && GetFieldType(m_field) != SECONDS_FIELD))
+ negate = " NOT ";
+
+ // boolean operators don't have any values in m_parameter, they work on the operator
+ if (m_operator == OPERATOR_FALSE || m_operator == OPERATOR_TRUE)
+ return GetBooleanQuery(negate, strType);
+
+ // Process boolean field with (not) EQUAL/CONTAINS "true"/"false" parameter too
+ if (GetFieldType(m_field) == BOOLEAN_FIELD &&
+ (m_parameter[0] == "true" || m_parameter[0] == "false") &&
+ (op == OPERATOR_CONTAINS || op == OPERATOR_EQUALS || op == OPERATOR_DOES_NOT_CONTAIN ||
+ op == OPERATOR_DOES_NOT_EQUAL))
+ {
+ if (m_parameter[0] == "false")
+ {
+ if (!negate.empty())
+ negate.clear();
+ else
+ negate = " NOT ";
+ }
+ return GetBooleanQuery(negate, strType);
+ }
+
+ // The BETWEEN operator is handled special
+ if (op == OPERATOR_BETWEEN)
+ {
+ if (m_parameter.size() != 2)
+ return "";
+
+ FIELD_TYPE fieldType = GetFieldType(m_field);
+ if (fieldType == REAL_FIELD)
+ return db.PrepareSQL("%s BETWEEN %s AND %s", GetField(m_field, strType).c_str(),
+ m_parameter[0].c_str(), m_parameter[1].c_str());
+ else if (fieldType == NUMERIC_FIELD)
+ return db.PrepareSQL("CAST(%s as DECIMAL(5,1)) BETWEEN %s AND %s",
+ GetField(m_field, strType).c_str(), m_parameter[0].c_str(),
+ m_parameter[1].c_str());
+ else if (fieldType == SECONDS_FIELD)
+ return db.PrepareSQL("CAST(%s as INTEGER) BETWEEN %s AND %s",
+ GetField(m_field, strType).c_str(), m_parameter[0].c_str(),
+ m_parameter[1].c_str());
+ else
+ return db.PrepareSQL("%s BETWEEN '%s' AND '%s'", GetField(m_field, strType).c_str(),
+ m_parameter[0].c_str(), m_parameter[1].c_str());
+ }
+
+ // now the query parameter
+ std::string wholeQuery;
+ for (std::vector<std::string>::const_iterator it = m_parameter.begin(); it != m_parameter.end();
+ ++it)
+ {
+ std::string query = '(' + FormatWhereClause(negate, operatorString, *it, db, strType) + ')';
+
+ if (it + 1 != m_parameter.end())
+ {
+ if (negate.empty())
+ query += " OR ";
+ else
+ query += " AND ";
+ }
+
+ wholeQuery += query;
+ }
+
+ return wholeQuery;
+}
+
+std::string CDatabaseQueryRule::FormatWhereClause(const std::string& negate,
+ const std::string& oper,
+ const std::string& param,
+ const CDatabase& db,
+ const std::string& strType) const
+{
+ std::string parameter = FormatParameter(oper, param, db, strType);
+
+ std::string query;
+ if (m_field != 0)
+ {
+ std::string fmt = "{}";
+ if (GetFieldType(m_field) == NUMERIC_FIELD)
+ fmt = "CAST({} as DECIMAL(6,1))";
+ else if (GetFieldType(m_field) == SECONDS_FIELD)
+ fmt = "CAST({} as INTEGER)";
+
+ query = StringUtils::Format(fmt, GetField(m_field, strType));
+ query += negate + parameter;
+
+ // special case for matching parameters in fields that might be either empty or NULL.
+ if ((param.empty() && negate.empty()) || (!param.empty() && !negate.empty()))
+ query += " OR " + GetField(m_field, strType) + " IS NULL";
+ }
+
+ if (query == negate + parameter)
+ query = "1";
+ return query;
+}
+
+void CDatabaseQueryRuleCombination::clear()
+{
+ m_combinations.clear();
+ m_rules.clear();
+ m_type = CombinationAnd;
+}
+
+std::string CDatabaseQueryRuleCombination::GetWhereClause(const CDatabase& db,
+ const std::string& strType) const
+{
+ std::string rule;
+
+ // translate the combinations into SQL
+ for (CDatabaseQueryRuleCombinations::const_iterator it = m_combinations.begin();
+ it != m_combinations.end(); ++it)
+ {
+ if (it != m_combinations.begin())
+ rule += m_type == CombinationAnd ? " AND " : " OR ";
+ rule += "(" + (*it)->GetWhereClause(db, strType) + ")";
+ }
+
+ // translate the rules into SQL
+ for (const auto& it : m_rules)
+ {
+ if (!rule.empty())
+ rule += m_type == CombinationAnd ? " AND " : " OR ";
+ rule += "(";
+ std::string currentRule = it->GetWhereClause(db, strType);
+ // if we don't get a rule, we add '1' or '0' so the query is still valid and doesn't fail
+ if (currentRule.empty())
+ currentRule = m_type == CombinationAnd ? "'1'" : "'0'";
+ rule += currentRule;
+ rule += ")";
+ }
+
+ return rule;
+}
+
+bool CDatabaseQueryRuleCombination::Load(const CVariant& obj,
+ const IDatabaseQueryRuleFactory* factory)
+{
+ if (!obj.isObject() && !obj.isArray())
+ return false;
+
+ CVariant child;
+ if (obj.isObject())
+ {
+ if (obj.isMember("and") && obj["and"].isArray())
+ {
+ m_type = CombinationAnd;
+ child = obj["and"];
+ }
+ else if (obj.isMember("or") && obj["or"].isArray())
+ {
+ m_type = CombinationOr;
+ child = obj["or"];
+ }
+ else
+ return false;
+ }
+ else
+ child = obj;
+
+ for (CVariant::const_iterator_array it = child.begin_array(); it != child.end_array(); ++it)
+ {
+ if (!it->isObject())
+ continue;
+
+ if (it->isMember("and") || it->isMember("or"))
+ {
+ std::shared_ptr<CDatabaseQueryRuleCombination> combo(factory->CreateCombination());
+ if (combo && combo->Load(*it, factory))
+ m_combinations.push_back(combo);
+ }
+ else
+ {
+ std::shared_ptr<CDatabaseQueryRule> rule(factory->CreateRule());
+ if (rule && rule->Load(*it))
+ m_rules.push_back(rule);
+ }
+ }
+
+ return true;
+}
+
+bool CDatabaseQueryRuleCombination::Save(TiXmlNode* parent) const
+{
+ for (const auto& it : m_rules)
+ it->Save(parent);
+ return true;
+}
+
+bool CDatabaseQueryRuleCombination::Save(CVariant& obj) const
+{
+ if (!obj.isObject() || (m_combinations.empty() && m_rules.empty()))
+ return false;
+
+ CVariant comboArray(CVariant::VariantTypeArray);
+ if (!m_combinations.empty())
+ {
+ for (const auto& combo : m_combinations)
+ {
+ CVariant comboObj(CVariant::VariantTypeObject);
+ if (combo->Save(comboObj))
+ comboArray.push_back(comboObj);
+ }
+ }
+ if (!m_rules.empty())
+ {
+ for (const auto& rule : m_rules)
+ {
+ CVariant ruleObj(CVariant::VariantTypeObject);
+ if (rule->Save(ruleObj))
+ comboArray.push_back(ruleObj);
+ }
+ }
+
+ obj[TranslateCombinationType()] = comboArray;
+
+ return true;
+}
+
+std::string CDatabaseQueryRuleCombination::TranslateCombinationType() const
+{
+ return m_type == CombinationAnd ? "and" : "or";
+}
diff --git a/xbmc/dbwrappers/DatabaseQuery.h b/xbmc/dbwrappers/DatabaseQuery.h
new file mode 100644
index 0000000..a15f377
--- /dev/null
+++ b/xbmc/dbwrappers/DatabaseQuery.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#define DATABASEQUERY_RULE_VALUE_SEPARATOR " / "
+
+class CDatabase;
+class CVariant;
+class TiXmlNode;
+
+class CDatabaseQueryRule
+{
+public:
+ CDatabaseQueryRule();
+ virtual ~CDatabaseQueryRule() = default;
+
+ enum SEARCH_OPERATOR
+ {
+ OPERATOR_START = 0,
+ OPERATOR_CONTAINS,
+ OPERATOR_DOES_NOT_CONTAIN,
+ OPERATOR_EQUALS,
+ OPERATOR_DOES_NOT_EQUAL,
+ OPERATOR_STARTS_WITH,
+ OPERATOR_ENDS_WITH,
+ OPERATOR_GREATER_THAN,
+ OPERATOR_LESS_THAN,
+ OPERATOR_AFTER,
+ OPERATOR_BEFORE,
+ OPERATOR_IN_THE_LAST,
+ OPERATOR_NOT_IN_THE_LAST,
+ OPERATOR_TRUE,
+ OPERATOR_FALSE,
+ OPERATOR_BETWEEN,
+ OPERATOR_END
+ };
+
+ enum FIELD_TYPE
+ {
+ TEXT_FIELD = 0,
+ REAL_FIELD,
+ NUMERIC_FIELD,
+ DATE_FIELD,
+ PLAYLIST_FIELD,
+ SECONDS_FIELD,
+ BOOLEAN_FIELD,
+ TEXTIN_FIELD
+ };
+
+ virtual bool Load(const TiXmlNode* node, const std::string& encoding = "UTF-8");
+ virtual bool Load(const CVariant& obj);
+ virtual bool Save(TiXmlNode* parent) const;
+ virtual bool Save(CVariant& obj) const;
+
+ static std::string GetLocalizedOperator(SEARCH_OPERATOR oper);
+ static void GetAvailableOperators(std::vector<std::string>& operatorList);
+
+ std::string GetParameter() const;
+ void SetParameter(const std::string& value);
+ void SetParameter(const std::vector<std::string>& values);
+
+ virtual std::string GetWhereClause(const CDatabase& db, const std::string& strType) const;
+
+ int m_field;
+ SEARCH_OPERATOR m_operator;
+ std::vector<std::string> m_parameter;
+
+protected:
+ virtual std::string GetField(int field, const std::string& type) const = 0;
+ virtual FIELD_TYPE GetFieldType(int field) const = 0;
+ virtual int TranslateField(const char* field) const = 0;
+ virtual std::string TranslateField(int field) const = 0;
+ std::string ValidateParameter(const std::string& parameter) const;
+ virtual std::string FormatParameter(const std::string& negate,
+ const std::string& oper,
+ const CDatabase& db,
+ const std::string& type) const;
+ virtual std::string FormatWhereClause(const std::string& negate,
+ const std::string& oper,
+ const std::string& param,
+ const CDatabase& db,
+ const std::string& type) const;
+ virtual SEARCH_OPERATOR GetOperator(const std::string& type) const { return m_operator; }
+ virtual std::string GetOperatorString(SEARCH_OPERATOR op) const;
+ virtual std::string GetBooleanQuery(const std::string& negate, const std::string& strType) const
+ {
+ return "";
+ }
+
+ static SEARCH_OPERATOR TranslateOperator(const char* oper);
+ static std::string TranslateOperator(SEARCH_OPERATOR oper);
+};
+
+class CDatabaseQueryRuleCombination;
+
+typedef std::vector<std::shared_ptr<CDatabaseQueryRule>> CDatabaseQueryRules;
+typedef std::vector<std::shared_ptr<CDatabaseQueryRuleCombination>> CDatabaseQueryRuleCombinations;
+
+class IDatabaseQueryRuleFactory
+{
+public:
+ virtual ~IDatabaseQueryRuleFactory() = default;
+ virtual CDatabaseQueryRule* CreateRule() const = 0;
+ virtual CDatabaseQueryRuleCombination* CreateCombination() const = 0;
+};
+
+class CDatabaseQueryRuleCombination
+{
+public:
+ virtual ~CDatabaseQueryRuleCombination() = default;
+
+ typedef enum
+ {
+ CombinationOr = 0,
+ CombinationAnd
+ } Combination;
+
+ void clear();
+ virtual bool Load(const TiXmlNode* node, const std::string& encoding = "UTF-8") { return false; }
+ virtual bool Load(const CVariant& obj, const IDatabaseQueryRuleFactory* factory);
+ virtual bool Save(TiXmlNode* parent) const;
+ virtual bool Save(CVariant& obj) const;
+
+ std::string GetWhereClause(const CDatabase& db, const std::string& strType) const;
+ std::string TranslateCombinationType() const;
+
+ Combination GetType() const { return m_type; }
+ void SetType(Combination combination) { m_type = combination; }
+
+ bool empty() const { return m_combinations.empty() && m_rules.empty(); }
+
+protected:
+ friend class CGUIDialogSmartPlaylistEditor;
+ friend class CGUIDialogMediaFilter;
+
+ Combination m_type = CombinationAnd;
+ CDatabaseQueryRuleCombinations m_combinations;
+ CDatabaseQueryRules m_rules;
+};
diff --git a/xbmc/dbwrappers/dataset.cpp b/xbmc/dbwrappers/dataset.cpp
new file mode 100644
index 0000000..4b4acb4
--- /dev/null
+++ b/xbmc/dbwrappers/dataset.cpp
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2004, Leo Seib, Hannover
+ *
+ * Project: C++ Dynamic Library
+ * Module: Dataset abstraction layer realisation file
+ * Author: Leo Seib E-Mail: leoseib@web.de
+ * Begin: 5/04/2002
+ *
+ * SPDX-License-Identifier: MIT
+ * See LICENSES/README.md for more information.
+ */
+
+#include "dataset.h"
+
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <cstring>
+
+#ifndef __GNUC__
+#pragma warning(disable : 4800)
+#endif
+
+namespace dbiplus
+{
+//************* Database implementation ***************
+
+Database::Database()
+ : error(), //S_NO_CONNECTION,
+ host(),
+ port(),
+ db(),
+ login(),
+ passwd(),
+ sequence_table("db_sequence")
+{
+ active = false; // No connection yet
+ compression = false;
+}
+
+Database::~Database()
+{
+ disconnect(); // Disconnect if connected to database
+}
+
+int Database::connectFull(const char* newHost,
+ const char* newPort,
+ const char* newDb,
+ const char* newLogin,
+ const char* newPasswd,
+ const char* newKey,
+ const char* newCert,
+ const char* newCA,
+ const char* newCApath,
+ const char* newCiphers,
+ bool newCompression)
+{
+ host = newHost;
+ port = newPort;
+ db = newDb;
+ login = newLogin;
+ passwd = newPasswd;
+ key = newKey;
+ cert = newCert;
+ ca = newCA;
+ capath = newCApath;
+ ciphers = newCiphers;
+ compression = newCompression;
+ return connect(true);
+}
+
+std::string Database::prepare(const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ std::string result = vprepare(format, args);
+ va_end(args);
+
+ return result;
+}
+
+//************* Dataset implementation ***************
+
+Dataset::Dataset() : select_sql("")
+{
+
+ db = NULL;
+ haveError = active = false;
+ frecno = 0;
+ fbof = feof = true;
+ autocommit = true;
+
+ fields_object = new Fields();
+
+ edit_object = new Fields();
+}
+
+Dataset::Dataset(Database* newDb) : select_sql("")
+{
+
+ db = newDb;
+ haveError = active = false;
+ frecno = 0;
+ fbof = feof = true;
+ autocommit = true;
+
+ fields_object = new Fields();
+
+ edit_object = new Fields();
+}
+
+Dataset::~Dataset()
+{
+ update_sql.clear();
+ insert_sql.clear();
+ delete_sql.clear();
+
+ delete fields_object;
+ delete edit_object;
+}
+
+void Dataset::setSqlParams(sqlType t, const char* sqlFrmt, ...)
+{
+ va_list ap;
+ char sqlCmd[DB_BUFF_MAX + 1];
+
+ va_start(ap, sqlFrmt);
+#ifndef TARGET_POSIX
+ _vsnprintf(sqlCmd, DB_BUFF_MAX - 1, sqlFrmt, ap);
+#else
+ vsnprintf(sqlCmd, DB_BUFF_MAX - 1, sqlFrmt, ap);
+#endif
+ va_end(ap);
+
+ switch (t)
+ {
+ case sqlSelect:
+ set_select_sql(sqlCmd);
+ break;
+ case sqlUpdate:
+ add_update_sql(sqlCmd);
+ break;
+ case sqlInsert:
+ add_insert_sql(sqlCmd);
+ break;
+ case sqlDelete:
+ add_delete_sql(sqlCmd);
+ break;
+ case sqlExec:
+ sql = sqlCmd;
+ break;
+ }
+}
+
+void Dataset::set_select_sql(const char* sel_sql)
+{
+ select_sql = sel_sql;
+}
+
+void Dataset::set_select_sql(const std::string& sel_sql)
+{
+ select_sql = sel_sql;
+}
+
+void Dataset::parse_sql(std::string& sql)
+{
+ std::string fpattern, by_what;
+ for (unsigned int i = 0; i < fields_object->size(); i++)
+ {
+ fpattern = ":OLD_" + (*fields_object)[i].props.name;
+ by_what = "'" + (*fields_object)[i].val.get_asString() + "'";
+ int idx = 0;
+ int next_idx = 0;
+ while ((idx = sql.find(fpattern, next_idx)) >= 0)
+ {
+ next_idx = idx + fpattern.size();
+ if (sql.length() > ((unsigned int)next_idx))
+ if (isalnum(sql[next_idx]) || sql[next_idx] == '_')
+ {
+ continue;
+ }
+ sql.replace(idx, fpattern.size(), by_what);
+ } //while
+ } //for
+
+ for (unsigned int i = 0; i < edit_object->size(); i++)
+ {
+ fpattern = ":NEW_" + (*edit_object)[i].props.name;
+ by_what = "'" + (*edit_object)[i].val.get_asString() + "'";
+ int idx = 0;
+ int next_idx = 0;
+ while ((idx = sql.find(fpattern, next_idx)) >= 0)
+ {
+ next_idx = idx + fpattern.size();
+ if (sql.length() > ((unsigned int)next_idx))
+ if (isalnum(sql[next_idx]) || sql[next_idx] == '_')
+ {
+ continue;
+ }
+ sql.replace(idx, fpattern.size(), by_what);
+ } //while
+ } //for
+}
+
+void Dataset::close(void)
+{
+ haveError = false;
+ frecno = 0;
+ fbof = feof = true;
+ active = false;
+
+ name2indexMap.clear();
+}
+
+bool Dataset::seek(int pos)
+{
+ frecno = (pos < num_rows() - 1) ? pos : num_rows() - 1;
+ frecno = (frecno < 0) ? 0 : frecno;
+ fbof = feof = (num_rows() == 0) ? true : false;
+ return ((bool)frecno);
+}
+
+void Dataset::refresh()
+{
+ int row = frecno;
+ if ((row != 0) && active)
+ {
+ close();
+ open();
+ seek(row);
+ }
+ else
+ open();
+}
+
+void Dataset::first()
+{
+ if (ds_state == dsSelect)
+ {
+ frecno = 0;
+ feof = fbof = (num_rows() > 0) ? false : true;
+ }
+}
+
+void Dataset::next()
+{
+ if (ds_state == dsSelect)
+ {
+ fbof = false;
+ if (frecno < num_rows() - 1)
+ {
+ frecno++;
+ feof = false;
+ }
+ else
+ feof = true;
+ if (num_rows() <= 0)
+ fbof = feof = true;
+ }
+}
+
+void Dataset::prev()
+{
+ if (ds_state == dsSelect)
+ {
+ feof = false;
+ if (frecno)
+ {
+ frecno--;
+ fbof = false;
+ }
+ else
+ fbof = true;
+ if (num_rows() <= 0)
+ fbof = feof = true;
+ }
+}
+
+void Dataset::last()
+{
+ if (ds_state == dsSelect)
+ {
+ frecno = (num_rows() > 0) ? num_rows() - 1 : 0;
+ feof = fbof = (num_rows() > 0) ? false : true;
+ }
+}
+
+bool Dataset::goto_rec(int pos)
+{
+ if (ds_state == dsSelect)
+ {
+ return seek(pos - 1);
+ }
+ return false;
+}
+
+void Dataset::insert()
+{
+ edit_object->resize(field_count());
+ for (int i = 0; i < field_count(); i++)
+ {
+ (*fields_object)[i].val = "";
+ (*edit_object)[i].val = "";
+ (*edit_object)[i].props = (*fields_object)[i].props;
+ }
+ ds_state = dsInsert;
+}
+
+void Dataset::edit()
+{
+ if (ds_state != dsSelect)
+ {
+ throw DbErrors("Editing is possible only when query exists!");
+ }
+ edit_object->resize(field_count());
+ for (unsigned int i = 0; i < fields_object->size(); i++)
+ {
+ (*edit_object)[i].props = (*fields_object)[i].props;
+ (*edit_object)[i].val = (*fields_object)[i].val;
+ }
+ ds_state = dsEdit;
+}
+
+void Dataset::post()
+{
+ if (ds_state == dsInsert)
+ make_insert();
+ else if (ds_state == dsEdit)
+ make_edit();
+}
+
+void Dataset::del()
+{
+ ds_state = dsDelete;
+}
+
+void Dataset::deletion()
+{
+ if (ds_state == dsDelete)
+ make_deletion();
+}
+
+bool Dataset::set_field_value(const char* f_name, const field_value& value)
+{
+ if ((ds_state == dsInsert) || (ds_state == dsEdit))
+ {
+ const int idx = fieldIndex(f_name);
+ if (idx >= 0)
+ {
+ (*edit_object)[idx].val = value;
+ return true;
+ }
+ throw DbErrors("Field not found: %s", f_name);
+ }
+ throw DbErrors("Not in Insert or Edit state");
+ // return false;
+}
+
+const field_value Dataset::get_field_value(const char* f_name)
+{
+ if (ds_state != dsInactive)
+ {
+ if (ds_state == dsEdit || ds_state == dsInsert)
+ {
+ const int idx = fieldIndex(f_name);
+ if (idx >= 0)
+ return (*edit_object)[idx].val;
+
+ throw DbErrors("Field not found: %s", f_name);
+ }
+ else
+ {
+ int idx = fieldIndex(f_name);
+ if (idx < 0)
+ {
+ const char* name = strstr(f_name, ".");
+ if (name)
+ name++;
+
+ if (name)
+ idx = fieldIndex(name);
+ }
+
+ if (idx >= 0)
+ return (*fields_object)[idx].val;
+
+ throw DbErrors("Field not found: %s", f_name);
+ }
+ }
+ throw DbErrors("Dataset state is Inactive");
+}
+
+const field_value Dataset::get_field_value(int index)
+{
+ if (ds_state != dsInactive)
+ {
+ if (ds_state == dsEdit || ds_state == dsInsert)
+ {
+ if (index < 0 || index >= field_count())
+ throw DbErrors("Field index not found: %d", index);
+
+ return (*edit_object)[index].val;
+ }
+ else
+ {
+ if (index < 0 || index >= field_count())
+ throw DbErrors("Field index not found: %d", index);
+
+ return (*fields_object)[index].val;
+ }
+ }
+ throw DbErrors("Dataset state is Inactive");
+}
+
+const sql_record* Dataset::get_sql_record()
+{
+ if (result.records.empty() || frecno >= (int)result.records.size())
+ return NULL;
+
+ return result.records[frecno];
+}
+
+const field_value Dataset::f_old(const char* f_name)
+{
+ if (ds_state != dsInactive)
+ for (int unsigned i = 0; i < fields_object->size(); i++)
+ if ((*fields_object)[i].props.name == f_name)
+ return (*fields_object)[i].val;
+ field_value fv;
+ return fv;
+}
+
+void Dataset::setParamList(const ParamList& params)
+{
+ plist = params;
+}
+
+bool Dataset::locate()
+{
+ bool result;
+ if (plist.empty())
+ return false;
+
+ std::map<std::string, field_value>::const_iterator i;
+ first();
+ while (!eof())
+ {
+ result = true;
+ for (i = plist.begin(); i != plist.end(); ++i)
+ if (fv(i->first.c_str()).get_asString() == i->second.get_asString())
+ {
+ continue;
+ }
+ else
+ {
+ result = false;
+ break;
+ }
+ if (result)
+ {
+ return result;
+ }
+ next();
+ }
+ return false;
+}
+
+bool Dataset::locate(const ParamList& params)
+{
+ plist = params;
+ return locate();
+}
+
+bool Dataset::findNext(void)
+{
+ bool result;
+ if (plist.empty())
+ return false;
+
+ std::map<std::string, field_value>::const_iterator i;
+ while (!eof())
+ {
+ result = true;
+ for (i = plist.begin(); i != plist.end(); ++i)
+ if (fv(i->first.c_str()).get_asString() == i->second.get_asString())
+ {
+ continue;
+ }
+ else
+ {
+ result = false;
+ break;
+ }
+ if (result)
+ {
+ return result;
+ }
+ next();
+ }
+ return false;
+}
+
+void Dataset::add_update_sql(const char* upd_sql)
+{
+ std::string s = upd_sql;
+ update_sql.push_back(s);
+}
+
+void Dataset::add_update_sql(const std::string& upd_sql)
+{
+ update_sql.push_back(upd_sql);
+}
+
+void Dataset::add_insert_sql(const char* ins_sql)
+{
+ std::string s = ins_sql;
+ insert_sql.push_back(s);
+}
+
+void Dataset::add_insert_sql(const std::string& ins_sql)
+{
+ insert_sql.push_back(ins_sql);
+}
+
+void Dataset::add_delete_sql(const char* del_sql)
+{
+ std::string s = del_sql;
+ delete_sql.push_back(s);
+}
+
+void Dataset::add_delete_sql(const std::string& del_sql)
+{
+ delete_sql.push_back(del_sql);
+}
+
+void Dataset::clear_update_sql()
+{
+ update_sql.clear();
+}
+
+void Dataset::clear_insert_sql()
+{
+ insert_sql.clear();
+}
+
+void Dataset::clear_delete_sql()
+{
+ delete_sql.clear();
+}
+
+size_t Dataset::insert_sql_count()
+{
+ return insert_sql.size();
+}
+
+size_t Dataset::delete_sql_count()
+{
+ return delete_sql.size();
+}
+
+int Dataset::field_count()
+{
+ return fields_object->size();
+}
+int Dataset::fieldCount()
+{
+ return fields_object->size();
+}
+
+const char* Dataset::fieldName(int n)
+{
+ if (n < field_count() && n >= 0)
+ return (*fields_object)[n].props.name.c_str();
+ else
+ return NULL;
+}
+
+char* Dataset::str_toLower(char* s)
+{
+ for (char* p = s; *p; p++)
+ *p = std::tolower(*p);
+
+ return s;
+}
+
+int Dataset::fieldIndex(const char* fn)
+{
+ std::string name(fn);
+ const auto it = name2indexMap.find(str_toLower(name.data()));
+ if (it != name2indexMap.end())
+ return (*it).second;
+ else
+ return -1;
+}
+
+//************* DbErrors implementation ***************
+
+DbErrors::DbErrors() : msg_("Unknown Database Error")
+{
+}
+
+DbErrors::DbErrors(const char* msg, ...)
+{
+ va_list vl;
+ va_start(vl, msg);
+ char buf[DB_BUFF_MAX] = "";
+#ifndef TARGET_POSIX
+ _vsnprintf(buf, DB_BUFF_MAX - 1, msg, vl);
+#else
+ vsnprintf(buf, DB_BUFF_MAX - 1, msg, vl);
+#endif
+ va_end(vl);
+ msg_ = "SQL: ";
+ msg_ += buf;
+
+ CLog::Log(LOGERROR, "{}", msg_);
+}
+
+const char* DbErrors::getMsg()
+{
+ return msg_.c_str();
+}
+
+} // namespace dbiplus
diff --git a/xbmc/dbwrappers/dataset.h b/xbmc/dbwrappers/dataset.h
new file mode 100644
index 0000000..5e420b1
--- /dev/null
+++ b/xbmc/dbwrappers/dataset.h
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2002, Leo Seib, Hannover
+ *
+ * Project:Dataset C++ Dynamic Library
+ * Module: Dataset abstraction layer header file
+ * Author: Leo Seib E-Mail: leoseib@web.de
+ * Begin: 5/04/2002
+ *
+ * SPDX-License-Identifier: MIT
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "qry_dat.h"
+
+#include <cstdio>
+#include <list>
+#include <map>
+#include <stdarg.h>
+#include <string>
+#include <unordered_map>
+
+namespace dbiplus
+{
+class Dataset; // forward declaration of class Dataset
+
+#define S_NO_CONNECTION "No active connection";
+
+#define DB_BUFF_MAX 8 * 1024 // Maximum buffer's capacity
+
+#define DB_CONNECTION_NONE 0
+#define DB_CONNECTION_OK 1
+#define DB_CONNECTION_BAD 2
+
+#define DB_COMMAND_OK 0 // OK - command executed
+#define DB_EMPTY_QUERY 1 // Query didn't return tuples
+#define DB_TUPLES_OK 2 // Query returned tuples
+#define DB_ERROR 5
+#define DB_BAD_RESPONSE 6
+#define DB_UNEXPECTED 7 // This shouldn't ever happen
+#define DB_UNEXPECTED_RESULT -1 //For integer functions
+
+/******************* Class Database definition ********************
+
+ represents connection with database server;
+
+******************************************************************/
+class Database
+{
+protected:
+ bool active;
+ bool compression;
+ std::string error, // Error description
+ host, port, db, login, passwd, //Login info
+ sequence_table, //Sequence table for nextid
+ default_charset, //Default character set
+ key, cert, ca, capath, ciphers; //SSL - Encryption info
+
+public:
+ /* constructor */
+ Database();
+ /* destructor */
+ virtual ~Database();
+ virtual Dataset* CreateDataset() const = 0;
+ /* sets a new host name */
+ virtual void setHostName(const char* newHost) { host = newHost; }
+ /* gets a host name */
+ const char* getHostName(void) const { return host.c_str(); }
+ /* sets a new port */
+ void setPort(const char* newPort) { port = newPort; }
+ /* gets a port */
+ const char* getPort(void) const { return port.c_str(); }
+ /* sets a new database name */
+ virtual void setDatabase(const char* newDb) { db = newDb; }
+ /* gets a database name */
+ const char* getDatabase(void) const { return db.c_str(); }
+ /* sets a new login to database */
+ void setLogin(const char* newLogin) { login = newLogin; }
+ /* gets a login */
+ const char* getLogin(void) const { return login.c_str(); }
+ /* sets a password */
+ void setPasswd(const char* newPasswd) { passwd = newPasswd; }
+ /* gets a password */
+ const char* getPasswd(void) const { return passwd.c_str(); }
+ /* active status is OK state */
+ virtual bool isActive(void) const { return active; }
+ /* Set new name of sequence table */
+ void setSequenceTable(const char* new_seq_table) { sequence_table = new_seq_table; }
+ /* Get name of sequence table */
+ const char* getSequenceTable(void) { return sequence_table.c_str(); }
+ /* Get the default character set */
+ const char* getDefaultCharset(void) { return default_charset.c_str(); }
+ /* Sets configuration */
+ virtual void setConfig(const char* newKey,
+ const char* newCert,
+ const char* newCA,
+ const char* newCApath,
+ const char* newCiphers,
+ bool newCompression)
+ {
+ key = newKey;
+ cert = newCert;
+ ca = newCA;
+ capath = newCApath;
+ ciphers = newCiphers;
+ compression = newCompression;
+ }
+
+ /* virtual methods that must be overloaded in derived classes */
+
+ virtual int init(void) { return DB_COMMAND_OK; }
+ virtual int status(void) { return DB_CONNECTION_NONE; }
+ virtual int setErr(int err_code, const char* qry) = 0;
+ virtual const char* getErrorMsg(void) { return error.c_str(); }
+
+ virtual int connect(bool create) { return DB_COMMAND_OK; }
+ virtual int connectFull(const char* newDb,
+ const char* newHost = NULL,
+ const char* newLogin = NULL,
+ const char* newPasswd = NULL,
+ const char* newPort = NULL,
+ const char* newKey = NULL,
+ const char* newCert = NULL,
+ const char* newCA = NULL,
+ const char* newCApath = NULL,
+ const char* newCiphers = NULL,
+ bool newCompression = false);
+ virtual void disconnect(void) { active = false; }
+ virtual int reset(void) { return DB_COMMAND_OK; }
+ virtual int create(void) { return DB_COMMAND_OK; }
+ virtual int drop(void) { return DB_COMMAND_OK; }
+ virtual long nextid(const char* seq_name) = 0;
+
+ /* \brief copy database */
+ virtual int copy(const char* new_name) { return -1; }
+
+ /* \brief drop all extra analytics from database */
+ virtual int drop_analytics(void) { return -1; }
+
+ virtual bool exists(void) { return false; }
+
+ /* virtual methods for transaction */
+
+ virtual void start_transaction() {}
+ virtual void commit_transaction() {}
+ virtual void rollback_transaction() {}
+
+ /* virtual methods for formatting */
+
+ /*! \brief Prepare a SQL statement for execution or querying using C printf nomenclature.
+ \param format - C printf compliant format string
+ \param ... - optional comma separated list of variables for substitution in format string placeholders.
+ \return escaped and formatted string.
+ */
+ virtual std::string prepare(const char* format, ...);
+
+ /*! \brief Prepare a SQL statement for execution or querying using C printf nomenclature
+ \param format - C printf compliant format string
+ \param args - va_list of variables for substitution in format string placeholders.
+ \return escaped and formatted string.
+ */
+ virtual std::string vprepare(const char* format, va_list args) = 0;
+
+ virtual bool in_transaction() { return false; }
+};
+
+/******************* Class Dataset definition *********************
+
+ global abstraction for using Databases
+
+******************************************************************/
+
+// define Dataset States type
+enum dsStates
+{
+ dsSelect,
+ dsInsert,
+ dsEdit,
+ dsUpdate,
+ dsDelete,
+ dsInactive
+};
+enum sqlType
+{
+ sqlSelect,
+ sqlUpdate,
+ sqlInsert,
+ sqlDelete,
+ sqlExec
+};
+
+typedef std::list<std::string> StringList;
+typedef std::map<std::string, field_value> ParamList;
+
+class Dataset
+{
+protected:
+ /* char *Host = ""; //WORK_HOST;
+ char *Database = ""; //WORK_DATABASE;
+ char *User = ""; //WORK_USER;
+ char *Password = ""; //WORK_PASSWORD;
+*/
+
+ Database* db; // info about db connection
+ dsStates ds_state; // current state
+ Fields *fields_object, *edit_object;
+ std::unordered_map<std::string, unsigned int>
+ name2indexMap; // Lower case field name -> database index
+
+ /* query results*/
+ result_set result;
+ result_set exec_res;
+ bool autorefresh;
+
+ bool active; // Is Query Opened?
+ bool haveError;
+ int frecno; // number of current row bei bewegung
+ std::string sql;
+
+ ParamList plist; // Paramlist for locate
+ bool fbof, feof;
+ bool autocommit; // for transactions
+
+ /* Variables to store SQL statements */
+ std::string empty_sql; // Executed when result set is empty
+ std::string select_sql; // May be only single string variable
+
+ StringList update_sql; // May be an array in complex queries
+ /* Field values for updating must has prefix :NEW_ and :OLD_ and field name
+ Example:
+ update wt_story set idobject set idobject=:NEW_idobject,body=:NEW_body
+ where idobject=:OLD_idobject
+ Essentially fields idobject and body must present in the
+ result set (select_sql statement) */
+
+ StringList insert_sql; // May be an array in complex queries
+ /* Field values for inserting must has prefix :NEW_ and field name
+ Example:
+ insert into wt_story (idobject, body) values (:NEW_idobject, :NEW_body)
+ Essentially fields idobject and body must present in the
+ result set (select_sql statement) */
+
+ StringList delete_sql; // May be an array in complex queries
+ /* Field values for deleing must has prefix :OLD_ and field name
+ Example:
+ delete from wt_story where idobject=:OLD_idobject
+ Essentially field idobject must present in the
+ result set (select_sql statement) */
+
+ /* Arrays for searching */
+ // StringList names, values;
+
+ /* Makes direct inserts into database via mysql_query function */
+ virtual void make_insert() = 0;
+ /* Edit SQL */
+ virtual void make_edit() = 0;
+ /* Delete SQL */
+ virtual void make_deletion() = 0;
+
+ /* This function works only with MySQL database
+ Filling the fields information from select statement */
+ virtual void fill_fields(void) = 0;
+
+ /* Parse Sql - replacing fields with prefixes :OLD_ and :NEW_ with current values of OLD or NEW field. */
+ void parse_sql(std::string& sql);
+
+ /* Returns old field value (for :OLD) */
+ virtual const field_value f_old(const char* f);
+
+ /* fast string tolower helper */
+ char* str_toLower(char* s);
+
+public:
+ /* constructor */
+ Dataset();
+ explicit Dataset(Database* newDb);
+
+ /* destructor */
+ virtual ~Dataset();
+
+ /* sets a new value of connection to database */
+ void setDatabase(Database* newDb) { db = newDb; }
+ /* retrieves a database which connected */
+ Database* getDatabase(void) { return db; }
+
+ /* sets a new query string to database server */
+ void setExecSql(const char* newSql) { sql = newSql; }
+ /* retrieves a query string */
+ const char* getExecSql(void) { return sql.c_str(); }
+
+ /* status active is OK query */
+ virtual bool isActive(void) { return active; }
+
+ virtual void setSqlParams(sqlType t, const char* sqlFrmt, ...);
+
+ /* error handling */
+ // virtual void halt(const char *msg);
+
+ /* last inserted id */
+ virtual int64_t lastinsertid() = 0;
+ /* sequence numbers */
+ virtual long nextid(const char* seq_name) = 0;
+ /* sequence numbers */
+ virtual int num_rows() = 0;
+
+ /* Open SQL query */
+ virtual void open(const std::string& sql) = 0;
+ virtual void open() = 0;
+ /* func. executes a query without results to return */
+ virtual int exec(const std::string& sql) = 0;
+ virtual int exec() = 0;
+ virtual const void* getExecRes() = 0;
+ /* as open, but with our query exec Sql */
+ virtual bool query(const std::string& sql) = 0;
+ /* Close SQL Query*/
+ virtual void close();
+ /* This function looks for field Field_name with value equal Field_value
+ Returns true if found (position of dataset is set to founded position)
+ and false another way (position is not changed). */
+ // virtual bool lookup(char *field_name, char*field_value);
+ /* Refresh dataset (reopen it and set the same cursor position) */
+ virtual void refresh();
+
+ /*! \brief Drop an index from the database table, provided it exists.
+ \param table - name of the table the index to be dropped is associated with
+ \param index - name of the index to be dropped
+ \return true when the index is guaranteed to no longer exist in the database.
+ */
+ virtual bool dropIndex(const char* table, const char* index) { return false; }
+
+ /* Go to record No (starting with 0) */
+ virtual bool seek(int pos = 0);
+ /* Go to record No (starting with 1) */
+ virtual bool goto_rec(int pos = 1);
+ /* Go to the first record in dataset */
+ virtual void first();
+ /* Go to next record in dataset */
+ virtual void next();
+ /* Go to previous record */
+ virtual void prev();
+ /* Go to last record in dataset */
+ virtual void last();
+
+ /* Check for Ending dataset */
+ virtual bool eof(void) { return feof; }
+ /* Check for Beginning dataset */
+ virtual bool bof(void) { return fbof; }
+
+ /* Start the insert mode */
+ virtual void insert();
+ /* Start the insert mode (alias for insert() function) */
+ virtual void append() { insert(); }
+ /* Start the edit mode */
+ virtual void edit();
+ /* Start the delete mode */
+ virtual void del();
+
+ /* Add changes, that were made during insert or edit states of dataset into the database */
+ virtual void post();
+ /* Delete statements from database */
+ virtual void deletion();
+ /* Cancel changes, made in insert or edit states of dataset */
+ virtual void cancel() {}
+ /* interrupt any pending database operation */
+ virtual void interrupt() {}
+
+ virtual void setParamList(const ParamList& params);
+ virtual bool locate();
+ virtual bool locate(const ParamList& params);
+ virtual bool findNext();
+
+ /* func. retrieves a number of fields */
+ /* Number of fields in a record */
+ virtual int field_count();
+ virtual int fieldCount();
+ /* func. retrieves a field name with 'n' index */
+ virtual const char* fieldName(int n);
+ /* func. retrieves a field index with 'fn' field name,return -1 when field name not found */
+ virtual int fieldIndex(const char* fn);
+
+ /* Set field value */
+ virtual bool set_field_value(const char* f_name, const field_value& value);
+ /* alias for set_field_value */
+ virtual bool sf(const char* f, const field_value& v) { return set_field_value(f, v); }
+
+ /* Return field name by it index */
+ // virtual char *field_name(int f_index) { return field_by_index(f_index)->get_field_name(); }
+
+ /* Getting value of field for current record */
+ virtual const field_value get_field_value(const char* f_name);
+ virtual const field_value get_field_value(int index);
+ /* Alias to get_field_value */
+ const field_value fv(const char* f) { return get_field_value(f); }
+ const field_value fv(int index) { return get_field_value(index); }
+
+ /* ------------ for transaction ------------------- */
+ void set_autocommit(bool v) { autocommit = v; }
+ bool get_autocommit() { return autocommit; }
+
+ /* ----------------- for debug -------------------- */
+ Fields* get_fields_object() { return fields_object; }
+ Fields* get_edit_object() { return edit_object; }
+
+ /* --------------- for fast access ---------------- */
+ const result_set& get_result_set() { return result; }
+ const sql_record* get_sql_record();
+
+private:
+ Dataset(const Dataset&) = delete;
+ Dataset& operator=(const Dataset&) = delete;
+
+ /* Get the column index from a string field_value request */
+ bool get_index_map_entry(const char* f_name);
+
+ void set_ds_state(dsStates new_state) { ds_state = new_state; }
+
+public:
+ /* return ds_state value */
+ dsStates get_state() { return ds_state; }
+
+ /*add a new value to select_sql*/
+ void set_select_sql(const char* sel_sql);
+ void set_select_sql(const std::string& select_sql);
+ /*add a new value to update_sql*/
+ void add_update_sql(const char* upd_sql);
+ void add_update_sql(const std::string& upd_sql);
+ /*add a new value to insert_sql*/
+ void add_insert_sql(const char* ins_sql);
+ void add_insert_sql(const std::string& ins_sql);
+ /*add a new value to delete_sql*/
+ void add_delete_sql(const char* del_sql);
+ void add_delete_sql(const std::string& del_sql);
+
+ /*clear update_sql*/
+ void clear_update_sql();
+ /*clear insert_sql*/
+ void clear_insert_sql();
+ /*clear delete_sql*/
+ void clear_delete_sql();
+
+ /* size of insert_sql*/
+ size_t insert_sql_count();
+ /* size of delete_sql*/
+ size_t delete_sql_count();
+
+ /*get value of select_sql*/
+ const char* get_select_sql();
+};
+
+/******************** Class DbErrors definition *********************
+
+ error handling
+
+******************************************************************/
+class DbErrors
+{
+
+public:
+ /* constructor */
+ DbErrors();
+ DbErrors(const char* msg, ...);
+
+ const char* getMsg();
+
+private:
+ std::string msg_;
+};
+
+} // namespace dbiplus
diff --git a/xbmc/dbwrappers/mysqldataset.cpp b/xbmc/dbwrappers/mysqldataset.cpp
new file mode 100644
index 0000000..a1a2d9d
--- /dev/null
+++ b/xbmc/dbwrappers/mysqldataset.cpp
@@ -0,0 +1,2099 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "mysqldataset.h"
+
+#include "Util.h"
+#include "network/DNSNameCache.h"
+#include "network/WakeOnAccess.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <array>
+#include <iostream>
+#include <set>
+#include <string>
+#ifdef HAS_MYSQL
+#include <mysql/errmsg.h>
+#elif defined(HAS_MARIADB)
+#include <mariadb/errmsg.h>
+#endif
+
+#ifdef TARGET_POSIX
+#include "platform/posix/ConvUtils.h"
+#endif
+
+#define MYSQL_OK 0
+#define ER_BAD_DB_ERROR 1049
+
+namespace dbiplus
+{
+
+//************* MysqlDatabase implementation ***************
+
+MysqlDatabase::MysqlDatabase()
+{
+
+ active = false;
+ _in_transaction = false; // for transaction
+
+ error = "Unknown database error"; //S_NO_CONNECTION;
+ host = "localhost";
+ port = "3306";
+ db = "mysql";
+ login = "root";
+ passwd = "null";
+ conn = NULL;
+ default_charset = "";
+}
+
+MysqlDatabase::~MysqlDatabase()
+{
+ disconnect();
+}
+
+Dataset* MysqlDatabase::CreateDataset() const
+{
+ return new MysqlDataset(const_cast<MysqlDatabase*>(this));
+}
+
+int MysqlDatabase::status(void)
+{
+ if (active == false)
+ return DB_CONNECTION_NONE;
+ return DB_CONNECTION_OK;
+}
+
+int MysqlDatabase::setErr(int err_code, const char* qry)
+{
+ switch (err_code)
+ {
+ case MYSQL_OK:
+ error = "Successful result";
+ break;
+ case CR_COMMANDS_OUT_OF_SYNC:
+ error = "Commands were executed in an improper order";
+ break;
+ case CR_SERVER_GONE_ERROR:
+ error = "The MySQL server has gone away";
+ break;
+ case CR_SERVER_LOST:
+ error = "The connection to the server was lost during this query";
+ break;
+ case CR_UNKNOWN_ERROR:
+ error = "An unknown error occurred";
+ break;
+ case 1146: /* ER_NO_SUCH_TABLE */
+ error = "The table does not exist";
+ break;
+ default:
+ char err[256];
+ snprintf(err, 256, "Undefined MySQL error: Code (%d)", err_code);
+ error = err;
+ }
+ error = "[" + db + "] " + error;
+ error += "\nQuery: ";
+ error += qry;
+ error += "\n";
+ return err_code;
+}
+
+const char* MysqlDatabase::getErrorMsg()
+{
+ return error.c_str();
+}
+
+void MysqlDatabase::configure_connection()
+{
+ char sqlcmd[512];
+ int ret;
+
+ // MySQL 5.7.5+: See #8393
+ strcpy(sqlcmd,
+ "SET SESSION sql_mode = (SELECT REPLACE(@@SESSION.sql_mode,'ONLY_FULL_GROUP_BY',''))");
+ if ((ret = mysql_real_query(conn, sqlcmd, strlen(sqlcmd))) != MYSQL_OK)
+ throw DbErrors("Can't disable sql_mode ONLY_FULL_GROUP_BY: '%s' (%d)", db.c_str(), ret);
+
+ // MySQL 5.7.6+: See #8393. Non-fatal if error, as not supported by MySQL 5.0.x
+ strcpy(sqlcmd, "SELECT @@SESSION.optimizer_switch");
+ if ((ret = mysql_real_query(conn, sqlcmd, strlen(sqlcmd))) == MYSQL_OK)
+ {
+ MYSQL_RES* res = mysql_store_result(conn);
+ MYSQL_ROW row;
+
+ if (res)
+ {
+ if ((row = mysql_fetch_row(res)) != NULL)
+ {
+ std::string column = row[0];
+ std::vector<std::string> split = StringUtils::Split(column, ',');
+
+ for (std::string& itIn : split)
+ {
+ if (StringUtils::Trim(itIn) == "derived_merge=on")
+ {
+ strcpy(sqlcmd, "SET SESSION optimizer_switch = 'derived_merge=off'");
+ if ((ret = mysql_real_query(conn, sqlcmd, strlen(sqlcmd))) != MYSQL_OK)
+ throw DbErrors("Can't set optimizer_switch = '%s': '%s' (%d)",
+ StringUtils::Trim(itIn).c_str(), db.c_str(), ret);
+ break;
+ }
+ }
+ }
+ mysql_free_result(res);
+ }
+ }
+ else
+ CLog::Log(LOGWARNING, "Unable to query optimizer_switch: '{}' ({})", db, ret);
+}
+
+int MysqlDatabase::connect(bool create_new)
+{
+ if (host.empty() || db.empty())
+ return DB_CONNECTION_NONE;
+
+ std::string resolvedHost;
+ if (!StringUtils::EqualsNoCase(host, "localhost") && CDNSNameCache::Lookup(host, resolvedHost))
+ {
+ CLog::Log(LOGDEBUG, "{} replacing configured host {} with resolved host {}", __FUNCTION__, host,
+ resolvedHost);
+ host = resolvedHost;
+ }
+
+ try
+ {
+ disconnect();
+
+ if (conn == NULL)
+ {
+ conn = mysql_init(conn);
+ mysql_ssl_set(conn, key.empty() ? NULL : key.c_str(), cert.empty() ? NULL : cert.c_str(),
+ ca.empty() ? NULL : ca.c_str(), capath.empty() ? NULL : capath.c_str(),
+ ciphers.empty() ? NULL : ciphers.c_str());
+ }
+
+ if (!CWakeOnAccess::GetInstance().WakeUpHost(host, "MySQL : " + db))
+ return DB_CONNECTION_NONE;
+
+ // establish connection with just user credentials
+ if (mysql_real_connect(conn, host.c_str(), login.c_str(), passwd.c_str(), NULL,
+ atoi(port.c_str()), NULL, compression ? CLIENT_COMPRESS : 0) != NULL)
+ {
+ static bool showed_ver_info = false;
+ if (!showed_ver_info)
+ {
+ std::string version_string = mysql_get_server_info(conn);
+ CLog::Log(LOGINFO, "MYSQL: Connected to version {}", version_string);
+ showed_ver_info = true;
+ unsigned long version = mysql_get_server_version(conn);
+ // Minimum for MySQL: 5.6 (5.5 is EOL)
+ unsigned long min_version = 50600;
+ if (version_string.find("MariaDB") != std::string::npos)
+ {
+ // Minimum for MariaDB: 5.5 (still supported)
+ min_version = 50500;
+ }
+
+ if (version < min_version)
+ {
+ CLog::Log(
+ LOGWARNING,
+ "MYSQL: Your database server version {} is very old and might not be supported in "
+ "future Kodi versions. Please consider upgrading to MySQL 5.7 or MariaDB 10.2.",
+ version_string);
+ }
+ }
+
+ // disable mysql autocommit since we handle it
+ //mysql_autocommit(conn, false);
+
+ // enforce utf8 charset usage
+ default_charset = mysql_character_set_name(conn);
+ if (mysql_set_character_set(conn, "utf8")) // returns 0 on success
+ {
+ CLog::Log(LOGERROR, "Unable to set utf8 charset: {} [{}]({})", db, mysql_errno(conn),
+ mysql_error(conn));
+ }
+
+ configure_connection();
+
+ // check existence
+ if (exists())
+ {
+ // nothing to see here
+ }
+ else if (create_new)
+ {
+ char sqlcmd[512];
+ int ret;
+
+ snprintf(sqlcmd, sizeof(sqlcmd),
+ "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci", db.c_str());
+ if ((ret = query_with_reconnect(sqlcmd)) != MYSQL_OK)
+ {
+ throw DbErrors("Can't create new database: '%s' (%d)", db.c_str(), ret);
+ }
+ }
+
+ if (mysql_select_db(conn, db.c_str()) == 0)
+ {
+ active = true;
+ return DB_CONNECTION_OK;
+ }
+ }
+
+ // if we failed above, either credentials were incorrect or the database didn't exist
+ if (mysql_errno(conn) == ER_BAD_DB_ERROR && create_new)
+ {
+
+ if (create() == MYSQL_OK)
+ {
+ active = true;
+
+ return DB_CONNECTION_OK;
+ }
+ }
+
+ CLog::Log(LOGERROR, "Unable to open database: {} [{}]({})", db, mysql_errno(conn),
+ mysql_error(conn));
+
+ return DB_CONNECTION_NONE;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "Unable to open database: {} ({})", db, GetLastError());
+ }
+ return DB_CONNECTION_NONE;
+}
+
+void MysqlDatabase::disconnect(void)
+{
+ if (conn != NULL)
+ {
+ mysql_close(conn);
+ conn = NULL;
+ }
+
+ active = false;
+}
+
+int MysqlDatabase::create()
+{
+ return connect(true);
+}
+
+int MysqlDatabase::drop()
+{
+ if (!active)
+ throw DbErrors("Can't drop database: no active connection...");
+ char sqlcmd[512];
+ int ret;
+ snprintf(sqlcmd, sizeof(sqlcmd), "DROP DATABASE `%s`", db.c_str());
+ if ((ret = query_with_reconnect(sqlcmd)) != MYSQL_OK)
+ {
+ throw DbErrors("Can't drop database: '%s' (%d)", db.c_str(), ret);
+ }
+ disconnect();
+ return DB_COMMAND_OK;
+}
+
+int MysqlDatabase::copy(const char* backup_name)
+{
+ if (!active || conn == NULL)
+ throw DbErrors("Can't copy database: no active connection...");
+
+ char sql[4096];
+ int ret;
+
+ // ensure we're connected to the db we are about to copy
+ if ((ret = mysql_select_db(conn, db.c_str())) != MYSQL_OK)
+ throw DbErrors("Can't connect to source database: '%s'", db.c_str());
+
+ // grab a list of base tables only (no views)
+ sprintf(sql, "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'");
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ throw DbErrors("Can't determine base tables for copy.");
+
+ // get list of all tables from old DB
+ MYSQL_RES* res = mysql_store_result(conn);
+
+ if (res)
+ {
+ if (mysql_num_rows(res) == 0)
+ {
+ mysql_free_result(res);
+ throw DbErrors("The source database was unexpectedly empty.");
+ }
+
+ // create the new database
+ snprintf(sql, sizeof(sql), "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci",
+ backup_name);
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ {
+ mysql_free_result(res);
+ throw DbErrors("Can't create database for copy: '%s' (%d)", db.c_str(), ret);
+ }
+
+ MYSQL_ROW row;
+
+ // duplicate each table from old db to new db
+ while ((row = mysql_fetch_row(res)) != NULL)
+ {
+ // copy the table definition
+ snprintf(sql, sizeof(sql), "CREATE TABLE `%s`.%s LIKE %s", backup_name, row[0], row[0]);
+
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ {
+ mysql_free_result(res);
+ throw DbErrors("Can't copy schema for table '%s'\nError: %d", row[0], ret);
+ }
+
+ // copy the table data
+ snprintf(sql, sizeof(sql), "INSERT INTO `%s`.%s SELECT * FROM %s", backup_name, row[0],
+ row[0]);
+
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ {
+ mysql_free_result(res);
+ throw DbErrors("Can't copy data for table '%s'\nError: %d", row[0], ret);
+ }
+ }
+ mysql_free_result(res);
+
+ // we don't recreate views, indices, or triggers on copy
+ // as we'll be dropping and recreating them anyway
+ }
+
+ return 1;
+}
+
+int MysqlDatabase::drop_analytics(void)
+{
+ if (!active || conn == NULL)
+ throw DbErrors("Can't clean database: no active connection...");
+
+ char sql[4096];
+ int ret;
+
+ // ensure we're connected to the db we are about to clean from stuff
+ if ((ret = mysql_select_db(conn, db.c_str())) != MYSQL_OK)
+ throw DbErrors("Can't connect to database: '%s'", db.c_str());
+
+ // getting a list of indexes in the database
+ snprintf(sql, sizeof(sql),
+ "SELECT DISTINCT table_name, index_name "
+ "FROM information_schema.statistics "
+ "WHERE index_name != 'PRIMARY' AND table_schema = '%s'",
+ db.c_str());
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ throw DbErrors("Can't determine list of indexes to drop.");
+
+ // we will acquire lists here
+ MYSQL_RES* res = mysql_store_result(conn);
+ MYSQL_ROW row;
+
+ if (res)
+ {
+ while ((row = mysql_fetch_row(res)) != NULL)
+ {
+ snprintf(sql, sizeof(sql), "ALTER TABLE `%s`.%s DROP INDEX %s", db.c_str(), row[0], row[1]);
+
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ {
+ mysql_free_result(res);
+ throw DbErrors("Can't drop index '%s'\nError: %d", row[0], ret);
+ }
+ }
+ mysql_free_result(res);
+ }
+
+ // next topic is a views list
+ snprintf(sql, sizeof(sql),
+ "SELECT table_name FROM information_schema.views WHERE table_schema = '%s'", db.c_str());
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ throw DbErrors("Can't determine list of views to drop.");
+
+ res = mysql_store_result(conn);
+
+ if (res)
+ {
+ while ((row = mysql_fetch_row(res)) != NULL)
+ {
+ /* we do not need IF EXISTS because these views are exist */
+ snprintf(sql, sizeof(sql), "DROP VIEW `%s`.%s", db.c_str(), row[0]);
+
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ {
+ mysql_free_result(res);
+ throw DbErrors("Can't drop view '%s'\nError: %d", row[0], ret);
+ }
+ }
+ mysql_free_result(res);
+ }
+
+ // triggers
+ snprintf(sql, sizeof(sql),
+ "SELECT trigger_name FROM information_schema.triggers WHERE event_object_schema = '%s'",
+ db.c_str());
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ throw DbErrors("Can't determine list of triggers to drop.");
+
+ res = mysql_store_result(conn);
+
+ if (res)
+ {
+ while ((row = mysql_fetch_row(res)) != NULL)
+ {
+ snprintf(sql, sizeof(sql), "DROP TRIGGER `%s`.%s", db.c_str(), row[0]);
+
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ {
+ mysql_free_result(res);
+ throw DbErrors("Can't drop trigger '%s'\nError: %d", row[0], ret);
+ }
+ }
+ mysql_free_result(res);
+ }
+
+ // Native functions
+ snprintf(sql, sizeof(sql),
+ "SELECT routine_name FROM information_schema.routines "
+ "WHERE routine_type = 'FUNCTION' and routine_schema = '%s'",
+ db.c_str());
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ throw DbErrors("Can't determine list of routines to drop.");
+
+ res = mysql_store_result(conn);
+
+ if (res)
+ {
+ while ((row = mysql_fetch_row(res)) != NULL)
+ {
+ snprintf(sql, sizeof(sql), "DROP FUNCTION `%s`.%s", db.c_str(), row[0]);
+
+ if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
+ {
+ mysql_free_result(res);
+ throw DbErrors("Can't drop function '%s'\nError: %d", row[0], ret);
+ }
+ }
+ mysql_free_result(res);
+ }
+
+ return 1;
+}
+
+int MysqlDatabase::query_with_reconnect(const char* query)
+{
+ int attempts = 5;
+ int result;
+
+ // try to reconnect if server is gone
+ while (((result = mysql_real_query(conn, query, strlen(query))) != MYSQL_OK) &&
+ ((result = mysql_errno(conn)) == CR_SERVER_GONE_ERROR || result == CR_SERVER_LOST) &&
+ (attempts-- > 0))
+ {
+ CLog::Log(LOGINFO, "MYSQL server has gone. Will try {} more attempt(s) to reconnect.",
+ attempts);
+ active = false;
+ connect(true);
+ }
+
+ return result;
+}
+
+long MysqlDatabase::nextid(const char* sname)
+{
+ CLog::Log(LOGDEBUG, "MysqlDatabase::nextid for {}", sname);
+ if (!active)
+ return DB_UNEXPECTED_RESULT;
+ const char* seq_table = "sys_seq";
+ int id; /*,nrow,ncol;*/
+ MYSQL_RES* res;
+ char sqlcmd[512];
+ snprintf(sqlcmd, sizeof(sqlcmd), "SELECT nextid FROM %s WHERE seq_name = '%s'", seq_table, sname);
+ CLog::Log(LOGDEBUG, "MysqlDatabase::nextid will request");
+ if ((last_err = query_with_reconnect(sqlcmd)) != 0)
+ {
+ return DB_UNEXPECTED_RESULT;
+ }
+ res = mysql_store_result(conn);
+ if (res)
+ {
+ if (mysql_num_rows(res) == 0)
+ {
+ id = 1;
+ snprintf(sqlcmd, sizeof(sqlcmd), "INSERT INTO %s (nextid,seq_name) VALUES (%d,'%s')",
+ seq_table, id, sname);
+ mysql_free_result(res);
+ if ((last_err = query_with_reconnect(sqlcmd)) != 0)
+ return DB_UNEXPECTED_RESULT;
+ return id;
+ }
+ else
+ {
+ id = -1;
+ snprintf(sqlcmd, sizeof(sqlcmd), "UPDATE %s SET nextid=%d WHERE seq_name = '%s'", seq_table,
+ id, sname);
+ mysql_free_result(res);
+ if ((last_err = query_with_reconnect(sqlcmd)) != 0)
+ return DB_UNEXPECTED_RESULT;
+ return id;
+ }
+ }
+ return DB_UNEXPECTED_RESULT;
+}
+
+// methods for transactions
+// ---------------------------------------------
+void MysqlDatabase::start_transaction()
+{
+ if (active)
+ {
+ mysql_autocommit(conn, false);
+ CLog::Log(LOGDEBUG, "Mysql Start transaction");
+ _in_transaction = true;
+ }
+}
+
+void MysqlDatabase::commit_transaction()
+{
+ if (active)
+ {
+ mysql_commit(conn);
+ mysql_autocommit(conn, true);
+ CLog::Log(LOGDEBUG, "Mysql commit transaction");
+ _in_transaction = false;
+ }
+}
+
+void MysqlDatabase::rollback_transaction()
+{
+ if (active)
+ {
+ mysql_rollback(conn);
+ mysql_autocommit(conn, true);
+ CLog::Log(LOGDEBUG, "Mysql rollback transaction");
+ _in_transaction = false;
+ }
+}
+
+bool MysqlDatabase::exists(void)
+{
+ bool ret = false;
+
+ if (conn == NULL || mysql_ping(conn))
+ {
+ CLog::Log(LOGERROR, "Not connected to database, test of existence is not possible.");
+ return ret;
+ }
+
+ MYSQL_RES* result = mysql_list_dbs(conn, db.c_str());
+ if (result == NULL)
+ {
+ CLog::Log(LOGERROR, "Database is not present, does the user has CREATE DATABASE permission");
+ return false;
+ }
+
+ ret = (mysql_num_rows(result) > 0);
+ mysql_free_result(result);
+
+ // Check if there is some tables ( to permit user with no create database rights
+ if (ret)
+ {
+ result = mysql_list_tables(conn, NULL);
+ if (result != NULL)
+ ret = (mysql_num_rows(result) > 0);
+
+ mysql_free_result(result);
+ }
+
+ return ret;
+}
+
+// methods for formatting
+// ---------------------------------------------
+std::string MysqlDatabase::vprepare(const char* format, va_list args)
+{
+ std::string strFormat = format;
+ std::string strResult = "";
+ size_t pos;
+
+ // %q is the sqlite format string for %s.
+ // Any bad character, like "'", will be replaced with a proper one
+ pos = 0;
+ while ((pos = strFormat.find("%s", pos)) != std::string::npos)
+ strFormat.replace(pos++, 2, "%q");
+
+ strResult = mysql_vmprintf(strFormat.c_str(), args);
+ // RAND() is the mysql form of RANDOM()
+ pos = 0;
+ while ((pos = strResult.find("RANDOM()", pos)) != std::string::npos)
+ {
+ strResult.replace(pos++, 8, "RAND()");
+ pos += 6;
+ }
+
+ // Replace some dataypes in CAST statements:
+ // before: CAST(iFoo AS TEXT), CAST(foo AS INTEGER)
+ // after: CAST(iFoo AS CHAR), CAST(foo AS SIGNED INTEGER)
+ pos = strResult.find("CAST(");
+ while (pos != std::string::npos)
+ {
+ size_t pos2 = strResult.find(" AS TEXT)", pos + 1);
+ if (pos2 != std::string::npos)
+ strResult.replace(pos2, 9, " AS CHAR)");
+ else
+ {
+ pos2 = strResult.find(" AS INTEGER)", pos + 1);
+ if (pos2 != std::string::npos)
+ strResult.replace(pos2, 12, " AS SIGNED INTEGER)");
+ }
+ pos = strResult.find("CAST(", pos + 1);
+ }
+
+ // Remove COLLATE NOCASE the SQLite case insensitive collation.
+ // In MySQL all tables are defined with case insensitive collation utf8_general_ci
+ pos = 0;
+ while ((pos = strResult.find(" COLLATE NOCASE", pos)) != std::string::npos)
+ strResult.erase(pos++, 15);
+
+ // Remove COLLATE ALPHANUM the SQLite custom collation.
+ pos = 0;
+ while ((pos = strResult.find(" COLLATE ALPHANUM", pos)) != std::string::npos)
+ strResult.erase(pos++, 15);
+
+ return strResult;
+}
+
+/* vsprintf() functionality is based on sqlite3.c functions */
+
+/*
+** Conversion types fall into various categories as defined by the
+** following enumeration.
+*/
+#define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */
+#define etFLOAT 2 /* Floating point. %f */
+#define etEXP 3 /* Exponential notation. %e and %E */
+#define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */
+#define etSIZE 5 /* Return number of characters processed so far. %n */
+#define etSTRING 6 /* Strings. %s */
+#define etDYNSTRING 7 /* Dynamically allocated strings. %z */
+#define etPERCENT 8 /* Percent symbol. %% */
+#define etCHARX 9 /* Characters. %c */
+/* The rest are extensions, not normally found in printf() */
+#define etSQLESCAPE 10 /* Strings with '\'' doubled. Strings with '\\' escaped. %q */
+#define etSQLESCAPE2 \
+ 11 /* Strings with '\'' doubled and enclosed in '',
+ NULL pointers replaced by SQL NULL. %Q */
+#define etPOINTER 14 /* The %p conversion */
+#define etSQLESCAPE3 15 /* %w -> Strings with '\"' doubled */
+
+#define etINVALID 0 /* Any unrecognized conversion type */
+
+/*
+** An "etByte" is an 8-bit unsigned value.
+*/
+typedef unsigned char etByte;
+
+/*
+** Each builtin conversion character (ex: the 'd' in "%d") is described
+** by an instance of the following structure
+*/
+typedef struct et_info
+{ /* Information about each format field */
+ char fmttype; /* The format field code letter */
+ etByte base; /* The base for radix conversion */
+ etByte flags; /* One or more of FLAG_ constants below */
+ etByte type; /* Conversion paradigm */
+ etByte charset; /* Offset into aDigits[] of the digits string */
+ etByte prefix; /* Offset into aPrefix[] of the prefix string */
+} et_info;
+
+/*
+** An objected used to accumulate the text of a string where we
+** do not necessarily know how big the string will be in the end.
+*/
+struct StrAccum
+{
+ char* zBase; /* A base allocation. Not from malloc. */
+ char* zText; /* The string collected so far */
+ int nChar; /* Length of the string so far */
+ int nAlloc; /* Amount of space allocated in zText */
+ int mxAlloc; /* Maximum allowed string length */
+ bool mallocFailed; /* Becomes true if any memory allocation fails */
+ bool tooBig; /* Becomes true if string size exceeds limits */
+};
+
+/*
+** Allowed values for et_info.flags
+*/
+#define FLAG_SIGNED 1 /* True if the value to convert is signed */
+#define FLAG_INTERN 2 /* True if for internal use only */
+#define FLAG_STRING 4 /* Allow infinity precision */
+
+/*
+** The following table is searched linearly, so it is good to put the
+** most frequently used conversion types first.
+*/
+static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
+static const char aPrefix[] = "-x0\000X0";
+// clang-format off
+constexpr std::array<et_info, 20> fmtinfo = {{
+ {'d', 10, 1, etRADIX, 0, 0},
+ {'s', 0, 4, etSTRING, 0, 0},
+ {'g', 0, 1, etGENERIC, 30, 0},
+ {'z', 0, 4, etDYNSTRING, 0, 0},
+ {'q', 0, 4, etSQLESCAPE, 0, 0},
+ {'Q', 0, 4, etSQLESCAPE2, 0, 0},
+ {'w', 0, 4, etSQLESCAPE3, 0, 0},
+ {'c', 0, 0, etCHARX, 0, 0},
+ {'o', 8, 0, etRADIX, 0, 2},
+ {'u', 10, 0, etRADIX, 0, 0},
+ {'x', 16, 0, etRADIX, 16, 1},
+ {'X', 16, 0, etRADIX, 0, 4},
+ {'f', 0, 1, etFLOAT, 0, 0},
+ {'e', 0, 1, etEXP, 30, 0},
+ {'E', 0, 1, etEXP, 14, 0},
+ {'G', 0, 1, etGENERIC, 14, 0},
+ {'i', 10, 1, etRADIX, 0, 0},
+ {'n', 0, 0, etSIZE, 0, 0},
+ {'%', 0, 0, etPERCENT, 0, 0},
+ {'p', 16, 0, etPOINTER, 0, 1},
+}};
+// clang-format on
+
+/*
+** "*val" is a double such that 0.1 <= *val < 10.0
+** Return the ascii code for the leading digit of *val, then
+** multiply "*val" by 10.0 to renormalize.
+**
+** Example:
+** input: *val = 3.14159
+** output: *val = 1.4159 function return = '3'
+**
+** The counter *cnt is incremented each time. After counter exceeds
+** 16 (the number of significant digits in a 64-bit float) '0' is
+** always returned.
+*/
+char MysqlDatabase::et_getdigit(double* val, int* cnt)
+{
+ int digit;
+ double d;
+ if ((*cnt)++ >= 16)
+ return '0';
+ digit = (int)*val;
+ d = digit;
+ digit += '0';
+ *val = (*val - d) * 10.0;
+ return (char)digit;
+}
+
+/*
+** Append N space characters to the given string buffer.
+*/
+void MysqlDatabase::appendSpace(StrAccum* pAccum, int N)
+{
+ static const char zSpaces[] = " ";
+ while (N >= (int)sizeof(zSpaces) - 1)
+ {
+ mysqlStrAccumAppend(pAccum, zSpaces, sizeof(zSpaces) - 1);
+ N -= sizeof(zSpaces) - 1;
+ }
+ if (N > 0)
+ {
+ mysqlStrAccumAppend(pAccum, zSpaces, N);
+ }
+}
+
+#ifndef MYSQL_PRINT_BUF_SIZE
+#define MYSQL_PRINT_BUF_SIZE 350
+#endif
+
+#define etBUFSIZE MYSQL_PRINT_BUF_SIZE /* Size of the output buffer */
+
+/*
+** The maximum length of a TEXT or BLOB in bytes. This also
+** limits the size of a row in a table or index.
+**
+** The hard limit is the ability of a 32-bit signed integer
+** to count the size: 2^31-1 or 2147483647.
+*/
+#ifndef MYSQL_MAX_LENGTH
+#define MYSQL_MAX_LENGTH 1000000000
+#endif
+
+/*
+** The root program. All variations call this core.
+**
+** INPUTS:
+** func This is a pointer to a function taking three arguments
+** 1. A pointer to anything. Same as the "arg" parameter.
+** 2. A pointer to the list of characters to be output
+** (Note, this list is NOT null terminated.)
+** 3. An integer number of characters to be output.
+** (Note: This number might be zero.)
+**
+** arg This is the pointer to anything which will be passed as the
+** first argument to "func". Use it for whatever you like.
+**
+** fmt This is the format string, as in the usual print.
+**
+** ap This is a pointer to a list of arguments. Same as in
+** vfprint.
+**
+** OUTPUTS:
+** The return value is the total number of characters sent to
+** the function "func". Returns -1 on a error.
+**
+** Note that the order in which automatic variables are declared below
+** seems to make a big difference in determining how fast this beast
+** will run.
+*/
+void MysqlDatabase::mysqlVXPrintf(StrAccum* pAccum, /* Accumulate results here */
+ int useExtended, /* Allow extended %-conversions */
+ const char* fmt, /* Format string */
+ va_list ap /* arguments */
+)
+{
+ int c; /* Next character in the format string */
+ char* bufpt; /* Pointer to the conversion buffer */
+ int precision; /* Precision of the current field */
+ int length; /* Length of the field */
+ int idx; /* A general purpose loop counter */
+ int width; /* Width of the current field */
+ etByte flag_leftjustify; /* True if "-" flag is present */
+ etByte flag_plussign; /* True if "+" flag is present */
+ etByte flag_blanksign; /* True if " " flag is present */
+ etByte flag_alternateform; /* True if "#" flag is present */
+ etByte flag_altform2; /* True if "!" flag is present */
+ etByte flag_zeropad; /* True if field width constant starts with zero */
+ etByte flag_long; /* True if "l" flag is present */
+ etByte flag_longlong; /* True if the "ll" flag is present */
+ etByte done; /* Loop termination flag */
+ uint64_t longvalue; /* Value for integer types */
+ double realvalue; /* Value for real types */
+ const et_info* infop; /* Pointer to the appropriate info structure */
+ char buf[etBUFSIZE]; /* Conversion buffer */
+ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */
+ etByte xtype = 0; /* Conversion paradigm */
+ char* zExtra; /* Extra memory used for etTCLESCAPE conversions */
+ int exp, e2; /* exponent of real numbers */
+ double rounder; /* Used for rounding floating point values */
+ etByte flag_dp; /* True if decimal point should be shown */
+ etByte flag_rtz; /* True if trailing zeros should be removed */
+ etByte flag_exp; /* True to force display of the exponent */
+ int nsd; /* Number of significant digits returned */
+
+ length = 0;
+ bufpt = 0;
+ for (; (c = (*fmt)) != 0; ++fmt)
+ {
+ bool isLike = false;
+ if (c != '%')
+ {
+ int amt;
+ bufpt = const_cast<char*>(fmt);
+ amt = 1;
+ while ((c = (*++fmt)) != '%' && c != 0)
+ amt++;
+ isLike = mysqlStrAccumAppend(pAccum, bufpt, amt);
+ if (c == 0)
+ break;
+ }
+ if ((c = (*++fmt)) == 0)
+ {
+ mysqlStrAccumAppend(pAccum, "%", 1);
+ break;
+ }
+ /* Find out what flags are present */
+ flag_leftjustify = flag_plussign = flag_blanksign = flag_alternateform = flag_altform2 =
+ flag_zeropad = 0;
+ done = 0;
+ do
+ {
+ switch (c)
+ {
+ case '-':
+ flag_leftjustify = 1;
+ break;
+ case '+':
+ flag_plussign = 1;
+ break;
+ case ' ':
+ flag_blanksign = 1;
+ break;
+ case '#':
+ flag_alternateform = 1;
+ break;
+ case '!':
+ flag_altform2 = 1;
+ break;
+ case '0':
+ flag_zeropad = 1;
+ break;
+ default:
+ done = 1;
+ break;
+ }
+ } while (!done && (c = (*++fmt)) != 0);
+ /* Get the field width */
+ width = 0;
+ if (c == '*')
+ {
+ width = va_arg(ap, int);
+ if (width < 0)
+ {
+ flag_leftjustify = 1;
+ width = -width;
+ }
+ c = *++fmt;
+ }
+ else
+ {
+ while (c >= '0' && c <= '9')
+ {
+ width = width * 10 + c - '0';
+ c = *++fmt;
+ }
+ }
+ if (width > etBUFSIZE - 10)
+ {
+ width = etBUFSIZE - 10;
+ }
+ /* Get the precision */
+ if (c == '.')
+ {
+ precision = 0;
+ c = *++fmt;
+ if (c == '*')
+ {
+ precision = va_arg(ap, int);
+ if (precision < 0)
+ precision = -precision;
+ c = *++fmt;
+ }
+ else
+ {
+ while (c >= '0' && c <= '9')
+ {
+ precision = precision * 10 + c - '0';
+ c = *++fmt;
+ }
+ }
+ }
+ else
+ {
+ precision = -1;
+ }
+ /* Get the conversion type modifier */
+ if (c == 'l')
+ {
+ flag_long = 1;
+ c = *++fmt;
+ if (c == 'l')
+ {
+ flag_longlong = 1;
+ c = *++fmt;
+ }
+ else
+ {
+ flag_longlong = 0;
+ }
+ }
+ else
+ {
+ flag_long = flag_longlong = 0;
+ }
+ /* Fetch the info entry for the field */
+ infop = fmtinfo.data();
+ xtype = etINVALID;
+
+ for (const auto& info : fmtinfo)
+ {
+ if (c != info.fmttype)
+ continue;
+
+ infop = &info;
+
+ if (useExtended || (infop->flags & FLAG_INTERN) == 0)
+ {
+ xtype = infop->type;
+ }
+ else
+ {
+ return;
+ }
+
+ break;
+ }
+
+ zExtra = 0;
+
+ /* Limit the precision to prevent overflowing buf[] during conversion */
+ if (precision > etBUFSIZE - 40 && (infop->flags & FLAG_STRING) == 0)
+ {
+ precision = etBUFSIZE - 40;
+ }
+
+ /*
+ ** At this point, variables are initialized as follows:
+ **
+ ** flag_alternateform TRUE if a '#' is present.
+ ** flag_altform2 TRUE if a '!' is present.
+ ** flag_plussign TRUE if a '+' is present.
+ ** flag_leftjustify TRUE if a '-' is present or if the
+ ** field width was negative.
+ ** flag_zeropad TRUE if the width began with 0.
+ ** flag_long TRUE if the letter 'l' (ell) prefixed
+ ** the conversion character.
+ ** flag_longlong TRUE if the letter 'll' (ell ell) prefixed
+ ** the conversion character.
+ ** flag_blanksign TRUE if a ' ' is present.
+ ** width The specified field width. This is
+ ** always non-negative. Zero is the default.
+ ** precision The specified precision. The default
+ ** is -1.
+ ** xtype The class of the conversion.
+ ** infop Pointer to the appropriate info struct.
+ */
+ switch (xtype)
+ {
+ case etPOINTER:
+ flag_longlong = sizeof(char*) == sizeof(int64_t);
+ flag_long = sizeof(char*) == sizeof(long int);
+ /* Fall through into the next case */
+ [[fallthrough]];
+ case etRADIX:
+ if (infop->flags & FLAG_SIGNED)
+ {
+ int64_t v;
+ if (flag_longlong)
+ {
+ v = va_arg(ap, int64_t);
+ }
+ else if (flag_long)
+ {
+ v = va_arg(ap, long int);
+ }
+ else
+ {
+ v = va_arg(ap, int);
+ }
+ if (v < 0)
+ {
+ longvalue = -v;
+ prefix = '-';
+ }
+ else
+ {
+ longvalue = v;
+ if (flag_plussign)
+ prefix = '+';
+ else if (flag_blanksign)
+ prefix = ' ';
+ else
+ prefix = 0;
+ }
+ }
+ else
+ {
+ if (flag_longlong)
+ {
+ longvalue = va_arg(ap, uint64_t);
+ }
+ else if (flag_long)
+ {
+ longvalue = va_arg(ap, unsigned long int);
+ }
+ else
+ {
+ longvalue = va_arg(ap, unsigned int);
+ }
+ prefix = 0;
+ }
+ if (longvalue == 0)
+ flag_alternateform = 0;
+ if (flag_zeropad && precision < width - (prefix != 0))
+ {
+ precision = width - (prefix != 0);
+ }
+ bufpt = &buf[etBUFSIZE - 1];
+ {
+ const char* cset;
+ int base;
+ cset = &aDigits[infop->charset];
+ base = infop->base;
+ do
+ { /* Convert to ascii */
+ *(--bufpt) = cset[longvalue % base];
+ longvalue = longvalue / base;
+ } while (longvalue > 0);
+ }
+ length = (int)(&buf[etBUFSIZE - 1] - bufpt);
+ for (idx = precision - length; idx > 0; idx--)
+ {
+ *(--bufpt) = '0'; /* Zero pad */
+ }
+ if (prefix)
+ *(--bufpt) = prefix; /* Add sign */
+ if (flag_alternateform && infop->prefix)
+ { /* Add "0" or "0x" */
+ const char* pre;
+ char x;
+ pre = &aPrefix[infop->prefix];
+ for (; (x = (*pre)) != 0; pre++)
+ *(--bufpt) = x;
+ }
+ length = (int)(&buf[etBUFSIZE - 1] - bufpt);
+ bufpt[length] = 0;
+ break;
+ case etFLOAT:
+ case etEXP:
+ case etGENERIC:
+ realvalue = va_arg(ap, double);
+ if (precision < 0)
+ precision = 6; /* Set default precision */
+ if (precision > etBUFSIZE / 2 - 10)
+ precision = etBUFSIZE / 2 - 10;
+ if (realvalue < 0.0)
+ {
+ realvalue = -realvalue;
+ prefix = '-';
+ }
+ else
+ {
+ if (flag_plussign)
+ prefix = '+';
+ else if (flag_blanksign)
+ prefix = ' ';
+ else
+ prefix = 0;
+ }
+ if (xtype == etGENERIC && precision > 0)
+ precision--;
+ /* It makes more sense to use 0.5 */
+ for (idx = precision, rounder = 0.5; idx > 0; idx--, rounder *= 0.1)
+ {
+ }
+ if (xtype == etFLOAT)
+ realvalue += rounder;
+ /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
+ exp = 0;
+#if 0
+ if( mysqlIsNaN((double)realvalue) ){
+ bufpt = "NaN";
+ length = 3;
+ break;
+ }
+#endif
+ if (realvalue > 0.0)
+ {
+ while (realvalue >= 1e32 && exp <= 350)
+ {
+ realvalue *= 1e-32;
+ exp += 32;
+ }
+ while (realvalue >= 1e8 && exp <= 350)
+ {
+ realvalue *= 1e-8;
+ exp += 8;
+ }
+ while (realvalue >= 10.0 && exp <= 350)
+ {
+ realvalue *= 0.1;
+ exp++;
+ }
+ while (realvalue < 1e-8)
+ {
+ realvalue *= 1e8;
+ exp -= 8;
+ }
+ while (realvalue < 1.0)
+ {
+ realvalue *= 10.0;
+ exp--;
+ }
+ if (exp > 350)
+ {
+ if (prefix == '-')
+ {
+ bufpt = const_cast<char*>("-Inf");
+ }
+ else if (prefix == '+')
+ {
+ bufpt = const_cast<char*>("+Inf");
+ }
+ else
+ {
+ bufpt = const_cast<char*>("Inf");
+ }
+ length = strlen(bufpt);
+ break;
+ }
+ }
+ bufpt = buf;
+ /*
+ ** If the field type is etGENERIC, then convert to either etEXP
+ ** or etFLOAT, as appropriate.
+ */
+ flag_exp = xtype == etEXP;
+ if (xtype != etFLOAT)
+ {
+ realvalue += rounder;
+ if (realvalue >= 10.0)
+ {
+ realvalue *= 0.1;
+ exp++;
+ }
+ }
+ if (xtype == etGENERIC)
+ {
+ flag_rtz = !flag_alternateform;
+ if (exp < -4 || exp > precision)
+ {
+ xtype = etEXP;
+ }
+ else
+ {
+ precision = precision - exp;
+ xtype = etFLOAT;
+ }
+ }
+ else
+ {
+ flag_rtz = 0;
+ }
+ if (xtype == etEXP)
+ {
+ e2 = 0;
+ }
+ else
+ {
+ e2 = exp;
+ }
+ nsd = 0;
+ flag_dp = (precision > 0 ? 1 : 0) | flag_alternateform | flag_altform2;
+ /* The sign in front of the number */
+ if (prefix)
+ {
+ *(bufpt++) = prefix;
+ }
+ /* Digits prior to the decimal point */
+ if (e2 < 0)
+ {
+ *(bufpt++) = '0';
+ }
+ else
+ {
+ for (; e2 >= 0; e2--)
+ {
+ *(bufpt++) = et_getdigit(&realvalue, &nsd);
+ }
+ }
+ /* The decimal point */
+ if (flag_dp)
+ {
+ *(bufpt++) = '.';
+ }
+ /* "0" digits after the decimal point but before the first
+ ** significant digit of the number */
+ for (e2++; e2 < 0; precision--, e2++)
+ {
+ //ASSERT( precision>0 );
+ *(bufpt++) = '0';
+ }
+ /* Significant digits after the decimal point */
+ while ((precision--) > 0)
+ {
+ *(bufpt++) = et_getdigit(&realvalue, &nsd);
+ }
+ /* Remove trailing zeros and the "." if no digits follow the "." */
+ if (flag_rtz && flag_dp)
+ {
+ while (bufpt[-1] == '0')
+ *(--bufpt) = 0;
+ //ASSERT( bufpt>buf );
+ if (bufpt[-1] == '.')
+ {
+ if (flag_altform2)
+ {
+ *(bufpt++) = '0';
+ }
+ else
+ {
+ *(--bufpt) = 0;
+ }
+ }
+ }
+ /* Add the "eNNN" suffix */
+ if (flag_exp || xtype == etEXP)
+ {
+ *(bufpt++) = aDigits[infop->charset];
+ if (exp < 0)
+ {
+ *(bufpt++) = '-';
+ exp = -exp;
+ }
+ else
+ {
+ *(bufpt++) = '+';
+ }
+ if (exp >= 100)
+ {
+ *(bufpt++) = (char)((exp / 100) + '0'); /* 100's digit */
+ exp %= 100;
+ }
+ *(bufpt++) = (char)(exp / 10 + '0'); /* 10's digit */
+ *(bufpt++) = (char)(exp % 10 + '0'); /* 1's digit */
+ }
+ *bufpt = 0;
+
+ /* The converted number is in buf[] and zero terminated. Output it.
+ ** Note that the number is in the usual order, not reversed as with
+ ** integer conversions. */
+ length = (int)(bufpt - buf);
+ bufpt = buf;
+
+ /* Special case: Add leading zeros if the flag_zeropad flag is
+ ** set and we are not left justified */
+ if (flag_zeropad && !flag_leftjustify && length < width)
+ {
+ int i;
+ int nPad = width - length;
+ for (i = width; i >= nPad; i--)
+ {
+ bufpt[i] = bufpt[i - nPad];
+ }
+ i = prefix != 0;
+ while (nPad--)
+ bufpt[i++] = '0';
+ length = width;
+ }
+ break;
+ case etSIZE:
+ *(va_arg(ap, int*)) = pAccum->nChar;
+ length = width = 0;
+ break;
+ case etPERCENT:
+ buf[0] = '%';
+ bufpt = buf;
+ length = 1;
+ break;
+ case etCHARX:
+ c = va_arg(ap, int);
+ buf[0] = (char)c;
+ if (precision >= 0)
+ {
+ for (idx = 1; idx < precision; idx++)
+ buf[idx] = (char)c;
+ length = precision;
+ }
+ else
+ {
+ length = 1;
+ }
+ bufpt = buf;
+ break;
+ case etSTRING:
+ case etDYNSTRING:
+ bufpt = va_arg(ap, char*);
+ if (bufpt == 0)
+ {
+ bufpt = const_cast<char*>("");
+ }
+ else if (xtype == etDYNSTRING)
+ {
+ zExtra = bufpt;
+ }
+ if (precision >= 0)
+ {
+ for (length = 0; length < precision && bufpt[length]; length++)
+ {
+ }
+ }
+ else
+ {
+ length = strlen(bufpt);
+ }
+ break;
+ case etSQLESCAPE:
+ case etSQLESCAPE2:
+ case etSQLESCAPE3:
+ {
+ int i, j, k, n, isnull;
+ int needQuote;
+ char ch;
+ char q = ((xtype == etSQLESCAPE3) ? '"' : '\''); /* Quote character */
+ std::string arg = va_arg(ap, char*);
+ if (isLike)
+ StringUtils::Replace(arg, "\\", "\\\\");
+ const char* escarg = arg.c_str();
+
+ isnull = escarg == 0;
+ if (isnull)
+ escarg = (xtype == etSQLESCAPE2 ? "NULL" : "(NULL)");
+ k = precision;
+ for (i = 0; k != 0 && (ch = escarg[i]) != 0; i++, k--)
+ ;
+ needQuote = !isnull && xtype == etSQLESCAPE2;
+ n = i * 2 + 1 + needQuote * 2;
+ if (n > etBUFSIZE)
+ {
+ bufpt = zExtra = (char*)malloc(n);
+ if (bufpt == 0)
+ {
+ pAccum->mallocFailed = true;
+ return;
+ }
+ }
+ else
+ {
+ bufpt = buf;
+ }
+ j = 0;
+ if (needQuote)
+ bufpt[j++] = q;
+ k = i;
+ j += mysql_real_escape_string(conn, bufpt, escarg, k);
+ if (needQuote)
+ bufpt[j++] = q;
+ bufpt[j] = 0;
+ length = j;
+ /* The precision in %q and %Q means how many input characters to
+ ** consume, not the length of the output...
+ ** if( precision>=0 && precision<length ) length = precision; */
+ break;
+ }
+ default:
+ {
+ return;
+ }
+ } /* End switch over the format type */
+ /*
+ ** The text of the conversion is pointed to by "bufpt" and is
+ ** "length" characters long. The field width is "width". Do
+ ** the output.
+ */
+ if (!flag_leftjustify)
+ {
+ int nspace;
+ nspace = width - length;
+ if (nspace > 0)
+ {
+ appendSpace(pAccum, nspace);
+ }
+ }
+ if (length > 0)
+ {
+ mysqlStrAccumAppend(pAccum, bufpt, length);
+ }
+ if (flag_leftjustify)
+ {
+ int nspace;
+ nspace = width - length;
+ if (nspace > 0)
+ {
+ appendSpace(pAccum, nspace);
+ }
+ }
+ if (zExtra)
+ {
+ free(zExtra);
+ }
+ } /* End for loop over the format string */
+} /* End of function */
+
+/*
+** Append N bytes of text from z to the StrAccum object.
+*/
+bool MysqlDatabase::mysqlStrAccumAppend(StrAccum* p, const char* z, int N)
+{
+ if (p->tooBig | p->mallocFailed)
+ {
+ return false;
+ }
+ if (N < 0)
+ {
+ N = strlen(z);
+ }
+ if (N == 0 || z == 0)
+ {
+ return false;
+ }
+ if (p->nChar + N >= p->nAlloc)
+ {
+ char* zNew;
+ int szNew = p->nChar;
+ szNew += N + 1;
+ if (szNew > p->mxAlloc)
+ {
+ mysqlStrAccumReset(p);
+ p->tooBig = true;
+ return false;
+ }
+ else
+ {
+ p->nAlloc = szNew;
+ }
+ zNew = (char*)malloc(p->nAlloc);
+ if (zNew)
+ {
+ memcpy(zNew, p->zText, p->nChar);
+ mysqlStrAccumReset(p);
+ p->zText = zNew;
+ }
+ else
+ {
+ p->mallocFailed = true;
+ mysqlStrAccumReset(p);
+ return false;
+ }
+ }
+
+ bool isLike = false;
+ std::string testString(z, N);
+ if (testString.find("LIKE") != std::string::npos || testString.find("like") != std::string::npos)
+ {
+ CLog::Log(LOGDEBUG,
+ "This query part contains a like, we will double backslash in the next field: {}",
+ testString);
+ isLike = true;
+ }
+
+ memcpy(&p->zText[p->nChar], z, N);
+ p->nChar += N;
+ return isLike;
+}
+
+/*
+** Finish off a string by making sure it is zero-terminated.
+** Return a pointer to the resulting string. Return a NULL
+** pointer if any kind of error was encountered.
+*/
+char* MysqlDatabase::mysqlStrAccumFinish(StrAccum* p)
+{
+ if (p->zText)
+ {
+ p->zText[p->nChar] = 0;
+ if (p->zText == p->zBase)
+ {
+ p->zText = (char*)malloc(p->nChar + 1);
+ if (p->zText)
+ {
+ memcpy(p->zText, p->zBase, p->nChar + 1);
+ }
+ else
+ {
+ p->mallocFailed = true;
+ }
+ }
+ }
+ return p->zText;
+}
+
+/*
+** Reset an StrAccum string. Reclaim all malloced memory.
+*/
+void MysqlDatabase::mysqlStrAccumReset(StrAccum* p)
+{
+ if (p->zText != p->zBase)
+ {
+ free(p->zText);
+ }
+ p->zText = 0;
+}
+
+/*
+** Initialize a string accumulator
+*/
+void MysqlDatabase::mysqlStrAccumInit(StrAccum* p, char* zBase, int n, int mx)
+{
+ p->zText = p->zBase = zBase;
+ p->nChar = 0;
+ p->nAlloc = n;
+ p->mxAlloc = mx;
+ p->tooBig = false;
+ p->mallocFailed = false;
+}
+
+/*
+** Print into memory obtained from mysql_malloc(). Omit the internal
+** %-conversion extensions.
+*/
+std::string MysqlDatabase::mysql_vmprintf(const char* zFormat, va_list ap)
+{
+ char zBase[MYSQL_PRINT_BUF_SIZE];
+ StrAccum acc;
+
+ mysqlStrAccumInit(&acc, zBase, sizeof(zBase), MYSQL_MAX_LENGTH);
+ mysqlVXPrintf(&acc, 0, zFormat, ap);
+ return mysqlStrAccumFinish(&acc);
+}
+
+//************* MysqlDataset implementation ***************
+
+MysqlDataset::MysqlDataset() : Dataset()
+{
+ haveError = false;
+ db = NULL;
+ autorefresh = false;
+}
+
+MysqlDataset::MysqlDataset(MysqlDatabase* newDb) : Dataset(newDb)
+{
+ haveError = false;
+ db = newDb;
+ autorefresh = false;
+}
+
+MysqlDataset::~MysqlDataset()
+{
+}
+
+void MysqlDataset::set_autorefresh(bool val)
+{
+ autorefresh = val;
+}
+
+//--------- protected functions implementation -----------------//
+
+MYSQL* MysqlDataset::handle()
+{
+ if (db != NULL)
+ {
+ return static_cast<MysqlDatabase*>(db)->getHandle();
+ }
+
+ return NULL;
+}
+
+void MysqlDataset::make_query(StringList& _sql)
+{
+ std::string query;
+ if (db == NULL)
+ throw DbErrors("No Database Connection");
+ try
+ {
+ if (autocommit)
+ db->start_transaction();
+
+ for (const std::string& i : _sql)
+ {
+ query = i;
+ Dataset::parse_sql(query);
+ if ((static_cast<MysqlDatabase*>(db)->query_with_reconnect(query.c_str())) != MYSQL_OK)
+ {
+ throw DbErrors(db->getErrorMsg());
+ }
+ } // end of for
+
+ if (db->in_transaction() && autocommit)
+ db->commit_transaction();
+
+ active = true;
+ ds_state = dsSelect;
+ if (autorefresh)
+ refresh();
+ } // end of try
+ catch (...)
+ {
+ if (db->in_transaction())
+ db->rollback_transaction();
+ throw;
+ }
+}
+
+void MysqlDataset::make_insert()
+{
+ make_query(insert_sql);
+ last();
+}
+
+void MysqlDataset::make_edit()
+{
+ make_query(update_sql);
+}
+
+void MysqlDataset::make_deletion()
+{
+ make_query(delete_sql);
+}
+
+void MysqlDataset::fill_fields()
+{
+ if ((db == NULL) || (result.record_header.empty()) ||
+ (result.records.size() < (unsigned int)frecno))
+ return;
+
+ if (fields_object->size() == 0) // Filling columns name
+ {
+ const unsigned int ncols = result.record_header.size();
+ fields_object->resize(ncols);
+ for (unsigned int i = 0; i < ncols; i++)
+ {
+ (*fields_object)[i].props = result.record_header[i];
+ std::string name = result.record_header[i].name;
+ name2indexMap.insert({str_toLower(name.data()), i});
+ }
+ }
+
+ //Filling result
+ if (result.records.size() != 0)
+ {
+ const sql_record* row = result.records[frecno];
+ if (row)
+ {
+ const unsigned int ncols = row->size();
+ fields_object->resize(ncols);
+ for (unsigned int i = 0; i < ncols; i++)
+ (*fields_object)[i].val = row->at(i);
+ return;
+ }
+ }
+ const unsigned int ncols = result.record_header.size();
+ fields_object->resize(ncols);
+ for (unsigned int i = 0; i < ncols; i++)
+ (*fields_object)[i].val = "";
+}
+
+//------------- public functions implementation -----------------//
+bool MysqlDataset::dropIndex(const char* table, const char* index)
+{
+ std::string sql;
+ std::string sql_prepared;
+
+ sql = "SELECT * FROM information_schema.statistics WHERE TABLE_SCHEMA=DATABASE() AND "
+ "table_name='%s' AND index_name='%s'";
+ sql_prepared = static_cast<MysqlDatabase*>(db)->prepare(sql.c_str(), table, index);
+
+ if (!query(sql_prepared))
+ return false;
+
+ if (num_rows())
+ {
+ sql = "ALTER TABLE %s DROP INDEX %s";
+ sql_prepared = static_cast<MysqlDatabase*>(db)->prepare(sql.c_str(), table, index);
+
+ if (exec(sql_prepared) != MYSQL_OK)
+ return false;
+ }
+
+ return true;
+}
+
+static bool ci_test(char l, char r)
+{
+ return tolower(l) == tolower(r);
+}
+
+static size_t ci_find(const std::string& where, const std::string& what)
+{
+ std::string::const_iterator loc =
+ std::search(where.begin(), where.end(), what.begin(), what.end(), ci_test);
+ if (loc == where.end())
+ return std::string::npos;
+ else
+ return loc - where.begin();
+}
+
+int MysqlDataset::exec(const std::string& sql)
+{
+ if (!handle())
+ throw DbErrors("No Database Connection");
+ std::string qry = sql;
+ int res = 0;
+ exec_res.clear();
+
+ // enforce the "auto_increment" keyword to be appended to "integer primary key"
+ size_t loc;
+
+ if ((loc = ci_find(qry, "integer primary key")) != std::string::npos)
+ {
+ qry = qry.insert(loc + 19, " auto_increment ");
+ }
+
+ // force the charset and collation to UTF-8
+ if (ci_find(qry, "CREATE TABLE") != std::string::npos ||
+ ci_find(qry, "CREATE TEMPORARY TABLE") != std::string::npos)
+ {
+ // If CREATE TABLE ... SELECT Syntax is used we need to add the encoding after the table before the select
+ // e.g. CREATE TABLE x CHARACTER SET utf8 COLLATE utf8_general_ci [AS] SELECT * FROM y
+ if ((loc = qry.find(" AS SELECT ")) != std::string::npos ||
+ (loc = qry.find(" SELECT ")) != std::string::npos)
+ {
+ qry = qry.insert(loc, " CHARACTER SET utf8 COLLATE utf8_general_ci");
+ }
+ else
+ qry += " CHARACTER SET utf8 COLLATE utf8_general_ci";
+ }
+
+ CLog::Log(LOGDEBUG, "Mysql execute: {}", qry);
+
+ if (db->setErr(static_cast<MysqlDatabase*>(db)->query_with_reconnect(qry.c_str()), qry.c_str()) !=
+ MYSQL_OK)
+ {
+ throw DbErrors(db->getErrorMsg());
+ }
+ else
+ {
+ //! @todo collect results and store in exec_res
+ return res;
+ }
+}
+
+int MysqlDataset::exec()
+{
+ return exec(sql);
+}
+
+const void* MysqlDataset::getExecRes()
+{
+ return &exec_res;
+}
+
+bool MysqlDataset::query(const std::string& query)
+{
+ if (!handle())
+ throw DbErrors("No Database Connection");
+ std::string qry = query;
+ int fs = qry.find("select");
+ int fS = qry.find("SELECT");
+ if (!(fs >= 0 || fS >= 0))
+ throw DbErrors("MUST be select SQL!");
+
+ close();
+
+ size_t loc;
+
+ // mysql doesn't understand CAST(foo as integer) => change to CAST(foo as signed integer)
+ while ((loc = ci_find(qry, "as integer)")) != std::string::npos)
+ qry = qry.insert(loc + 3, "signed ");
+
+ MYSQL_RES* stmt = NULL;
+
+ if (static_cast<MysqlDatabase*>(db)->setErr(
+ static_cast<MysqlDatabase*>(db)->query_with_reconnect(qry.c_str()), qry.c_str()) !=
+ MYSQL_OK)
+ throw DbErrors(db->getErrorMsg());
+
+ MYSQL* conn = handle();
+ stmt = mysql_store_result(conn);
+ if (stmt == NULL)
+ throw DbErrors("Missing result set!");
+
+ // column headers
+ const unsigned int numColumns = mysql_num_fields(stmt);
+ MYSQL_FIELD* fields = mysql_fetch_fields(stmt);
+ MYSQL_ROW row;
+ result.record_header.resize(numColumns);
+ for (unsigned int i = 0; i < numColumns; i++)
+ result.record_header[i].name = fields[i].name;
+
+ // returned rows
+ while ((row = mysql_fetch_row(stmt)))
+ { // have a row of data
+ sql_record* res = new sql_record;
+ res->resize(numColumns);
+ for (unsigned int i = 0; i < numColumns; i++)
+ {
+ field_value& v = res->at(i);
+ switch (fields[i].type)
+ {
+ case MYSQL_TYPE_LONGLONG:
+ if (row[i] != nullptr)
+ {
+ v.set_asInt64(strtoll(row[i], nullptr, 10));
+ }
+ else
+ {
+ v.set_asInt64(0);
+ }
+ break;
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
+ case MYSQL_TYPE_TINY:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_LONG:
+ if (row[i] != NULL)
+ {
+ v.set_asInt(atoi(row[i]));
+ }
+ else
+ {
+ v.set_asInt(0);
+ }
+ break;
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ if (row[i] != NULL)
+ {
+ v.set_asDouble(atof(row[i]));
+ }
+ else
+ {
+ v.set_asDouble(0);
+ }
+ break;
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_VARCHAR:
+ if (row[i] != NULL)
+ v.set_asString((const char*)row[i]);
+ break;
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ if (row[i] != NULL)
+ v.set_asString((const char*)row[i]);
+ break;
+ case MYSQL_TYPE_NULL:
+ default:
+ CLog::Log(LOGDEBUG, "MYSQL: Unknown field type: {}", fields[i].type);
+ v.set_asString("");
+ v.set_isNull();
+ break;
+ }
+ }
+ result.records.push_back(res);
+ }
+ mysql_free_result(stmt);
+ active = true;
+ ds_state = dsSelect;
+ this->first();
+ return true;
+}
+
+void MysqlDataset::open(const std::string& sql)
+{
+ set_select_sql(sql);
+ open();
+}
+
+void MysqlDataset::open()
+{
+ if (select_sql.size())
+ {
+ query(select_sql);
+ }
+ else
+ {
+ ds_state = dsInactive;
+ }
+}
+
+void MysqlDataset::close()
+{
+ Dataset::close();
+ result.clear();
+ edit_object->clear();
+ fields_object->clear();
+ ds_state = dsInactive;
+ active = false;
+}
+
+void MysqlDataset::cancel()
+{
+ if ((ds_state == dsInsert) || (ds_state == dsEdit))
+ {
+ if (result.record_header.size())
+ ds_state = dsSelect;
+ else
+ ds_state = dsInactive;
+ }
+}
+
+int MysqlDataset::num_rows()
+{
+ return result.records.size();
+}
+
+bool MysqlDataset::eof()
+{
+ return feof;
+}
+
+bool MysqlDataset::bof()
+{
+ return fbof;
+}
+
+void MysqlDataset::first()
+{
+ Dataset::first();
+ this->fill_fields();
+}
+
+void MysqlDataset::last()
+{
+ Dataset::last();
+ fill_fields();
+}
+
+void MysqlDataset::prev(void)
+{
+ Dataset::prev();
+ fill_fields();
+}
+
+void MysqlDataset::next(void)
+{
+ Dataset::next();
+ if (!eof())
+ fill_fields();
+}
+
+void MysqlDataset::free_row(void)
+{
+ if (frecno < 0 || (unsigned int)frecno >= result.records.size())
+ return;
+
+ sql_record* row = result.records[frecno];
+ if (row)
+ {
+ delete row;
+ result.records[frecno] = NULL;
+ }
+}
+
+bool MysqlDataset::seek(int pos)
+{
+ if (ds_state == dsSelect)
+ {
+ Dataset::seek(pos);
+ fill_fields();
+ return true;
+ }
+
+ return false;
+}
+
+int64_t MysqlDataset::lastinsertid()
+{
+ if (!handle())
+ throw DbErrors("No Database Connection");
+ return mysql_insert_id(handle());
+}
+
+long MysqlDataset::nextid(const char* seq_name)
+{
+ if (handle())
+ return db->nextid(seq_name);
+
+ return DB_UNEXPECTED_RESULT;
+}
+
+void MysqlDataset::interrupt()
+{
+ // Impossible
+}
+
+} // namespace dbiplus
diff --git a/xbmc/dbwrappers/mysqldataset.h b/xbmc/dbwrappers/mysqldataset.h
new file mode 100644
index 0000000..552368d
--- /dev/null
+++ b/xbmc/dbwrappers/mysqldataset.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "dataset.h"
+
+#include <stdio.h>
+#ifdef HAS_MYSQL
+#include <mysql/mysql.h>
+#elif defined(HAS_MARIADB)
+#include <mariadb/mysql.h>
+#endif
+
+namespace dbiplus
+{
+/***************** Class MysqlDatabase definition ******************
+
+ class 'MysqlDatabase' connects with MySQL-server
+
+******************************************************************/
+class MysqlDatabase : public Database
+{
+protected:
+ /* connect descriptor */
+ MYSQL* conn;
+ bool _in_transaction;
+ int last_err;
+
+public:
+ /* default constructor */
+ MysqlDatabase();
+ /* destructor */
+ ~MysqlDatabase() override;
+
+ Dataset* CreateDataset() const override;
+
+ /* func. returns connection handle with MySQL-server */
+ MYSQL* getHandle() { return conn; }
+ /* func. returns current status about MySQL-server connection */
+ int status() override;
+ int setErr(int err_code, const char* qry) override;
+ /* func. returns error message if error occurs */
+ const char* getErrorMsg() override;
+
+ /* func. connects to database-server */
+ int connect(bool create) override;
+ /* func. disconnects from database-server */
+ void disconnect() override;
+ /* func. creates new database */
+ int create() override;
+ /* func. deletes database */
+ int drop() override;
+ /* check if database exists (ie has tables/views defined) */
+ bool exists() override;
+
+ /* \brief copy database */
+ int copy(const char* backup_name) override;
+
+ /* \brief drop all extra analytics from database */
+ int drop_analytics(void) override;
+
+ long nextid(const char* seq_name) override;
+
+ /* virtual methods for transaction */
+
+ void start_transaction() override;
+ void commit_transaction() override;
+ void rollback_transaction() override;
+
+ /* virtual methods for formatting */
+ std::string vprepare(const char* format, va_list args) override;
+
+ bool in_transaction() override { return _in_transaction; }
+ int query_with_reconnect(const char* query);
+ void configure_connection();
+
+private:
+ typedef struct StrAccum StrAccum;
+
+ char et_getdigit(double* val, int* cnt);
+ void appendSpace(StrAccum* pAccum, int N);
+ void mysqlVXPrintf(StrAccum* pAccum, int useExtended, const char* fmt, va_list ap);
+ bool mysqlStrAccumAppend(StrAccum* p, const char* z, int N);
+ char* mysqlStrAccumFinish(StrAccum* p);
+ void mysqlStrAccumReset(StrAccum* p);
+ void mysqlStrAccumInit(StrAccum* p, char* zBase, int n, int mx);
+ std::string mysql_vmprintf(const char* zFormat, va_list ap);
+};
+
+/***************** Class MysqlDataset definition *******************
+
+ class 'MysqlDataset' does a query to MySQL-server
+
+******************************************************************/
+
+class MysqlDataset : public Dataset
+{
+protected:
+ MYSQL* handle();
+
+ /* Makes direct queries to database */
+ virtual void make_query(StringList& _sql);
+ /* Makes direct inserts into database */
+ void make_insert() override;
+ /* Edit SQL */
+ void make_edit() override;
+ /* Delete SQL */
+ void make_deletion() override;
+
+ /* This function works only with MySQL database
+ Filling the fields information from select statement */
+ void fill_fields() override;
+ /* Changing field values during dataset navigation */
+ virtual void free_row(); // free the memory allocated for the current row
+
+public:
+ /* constructor */
+ MysqlDataset();
+ explicit MysqlDataset(MysqlDatabase* newDb);
+
+ /* destructor */
+ ~MysqlDataset() override;
+
+ /* set autorefresh boolean value (if true - refresh the data after edit()
+or insert() operations default = false) */
+ void set_autorefresh(bool val);
+
+ /* opens a query & then sets a query results */
+ void open() override;
+ void open(const std::string& sql) override;
+ /* func. executes a query without results to return */
+ int exec() override;
+ int exec(const std::string& sql) override;
+ const void* getExecRes() override;
+ /* as open, but with our query exec Sql */
+ bool query(const std::string& query) override;
+ /* func. closes a query */
+ void close(void) override;
+ /* Cancel changes, made in insert or edit states of dataset */
+ void cancel() override;
+ /* last insert id */
+ int64_t lastinsertid() override;
+ /* sequence numbers */
+ long nextid(const char* seq_name) override;
+ /* sequence numbers */
+ int num_rows() override;
+ /* interrupt any pending database operation */
+ void interrupt() override;
+
+ bool bof() override;
+ bool eof() override;
+ void first() override;
+ void last() override;
+ void prev() override;
+ void next() override;
+ /* Go to record No (starting with 0) */
+ bool seek(int pos = 0) override;
+
+ bool dropIndex(const char* table, const char* index) override;
+};
+} // namespace dbiplus
diff --git a/xbmc/dbwrappers/qry_dat.cpp b/xbmc/dbwrappers/qry_dat.cpp
new file mode 100644
index 0000000..1abee3f
--- /dev/null
+++ b/xbmc/dbwrappers/qry_dat.cpp
@@ -0,0 +1,902 @@
+/*
+ * Copyright (C) 2004, Leo Seib, Hannover
+ *
+ * Project: C++ Dynamic Library
+ * Module: FieldValue class realisation file
+ * Author: Leo Seib E-Mail: leoseib@web.de
+ * Begin: 5/04/2002
+ *
+ * SPDX-License-Identifier: MIT
+ * See LICENSES/README.md for more information.
+ */
+
+/**********************************************************************
+ * 2005-03-29 - Minor modifications to allow get_asBool to function on
+ * on string values that are 1 or 0
+ **********************************************************************/
+
+#include "qry_dat.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef __GNUC__
+#pragma warning(disable : 4800)
+#pragma warning(disable : 4715)
+#endif
+
+namespace dbiplus
+{
+
+//Constructors
+field_value::field_value()
+{
+ field_type = ft_String;
+ is_null = false;
+}
+
+field_value::field_value(const char* s) : str_value(s)
+{
+ field_type = ft_String;
+ is_null = false;
+}
+
+field_value::field_value(const bool b)
+{
+ bool_value = b;
+ field_type = ft_Boolean;
+ is_null = false;
+}
+
+field_value::field_value(const char c)
+{
+ char_value = c;
+ field_type = ft_Char;
+ is_null = false;
+}
+
+field_value::field_value(const short s)
+{
+ short_value = s;
+ field_type = ft_Short;
+ is_null = false;
+}
+
+field_value::field_value(const unsigned short us)
+{
+ ushort_value = us;
+ field_type = ft_UShort;
+ is_null = false;
+}
+
+field_value::field_value(const int i)
+{
+ int_value = i;
+ field_type = ft_Int;
+ is_null = false;
+}
+
+field_value::field_value(const unsigned int ui)
+{
+ uint_value = ui;
+ field_type = ft_UInt;
+ is_null = false;
+}
+
+field_value::field_value(const float f)
+{
+ float_value = f;
+ field_type = ft_Float;
+ is_null = false;
+}
+
+field_value::field_value(const double d)
+{
+ double_value = d;
+ field_type = ft_Double;
+ is_null = false;
+}
+
+field_value::field_value(const int64_t i)
+{
+ int64_value = i;
+ field_type = ft_Int64;
+ is_null = false;
+}
+
+field_value::field_value(const field_value& fv)
+{
+ switch (fv.get_fType())
+ {
+ case ft_String:
+ {
+ set_asString(fv.get_asString());
+ break;
+ }
+ case ft_Boolean:
+ {
+ set_asBool(fv.get_asBool());
+ break;
+ }
+ case ft_Char:
+ {
+ set_asChar(fv.get_asChar());
+ break;
+ }
+ case ft_Short:
+ {
+ set_asShort(fv.get_asShort());
+ break;
+ }
+ case ft_UShort:
+ {
+ set_asUShort(fv.get_asUShort());
+ break;
+ }
+ case ft_Int:
+ {
+ set_asInt(fv.get_asInt());
+ break;
+ }
+ case ft_UInt:
+ {
+ set_asUInt(fv.get_asUInt());
+ break;
+ }
+ case ft_Float:
+ {
+ set_asFloat(fv.get_asFloat());
+ break;
+ }
+ case ft_Double:
+ {
+ set_asDouble(fv.get_asDouble());
+ break;
+ }
+ case ft_Int64:
+ {
+ set_asInt64(fv.get_asInt64());
+ break;
+ }
+ default:
+ break;
+ }
+ is_null = fv.get_isNull();
+}
+
+//empty destructor
+field_value::~field_value() = default;
+
+//Conversations functions
+std::string field_value::get_asString() const
+{
+ std::string tmp;
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ tmp = str_value;
+ return tmp;
+ }
+ case ft_Boolean:
+ {
+ if (bool_value)
+ return tmp = "True";
+ else
+ return tmp = "False";
+ }
+ case ft_Char:
+ {
+ return tmp = char_value;
+ }
+ case ft_Short:
+ {
+ char t[10];
+ sprintf(t, "%i", short_value);
+ return tmp = t;
+ }
+ case ft_UShort:
+ {
+ char t[10];
+ sprintf(t, "%i", ushort_value);
+ return tmp = t;
+ }
+ case ft_Int:
+ {
+ char t[12];
+ sprintf(t, "%d", int_value);
+ return tmp = t;
+ }
+ case ft_UInt:
+ {
+ char t[12];
+ sprintf(t, "%u", uint_value);
+ return tmp = t;
+ }
+ case ft_Float:
+ {
+ char t[16];
+ sprintf(t, "%f", static_cast<double>(float_value));
+ return tmp = t;
+ }
+ case ft_Double:
+ {
+ char t[32];
+ sprintf(t, "%f", double_value);
+ return tmp = t;
+ }
+ case ft_Int64:
+ {
+ char t[23];
+ sprintf(t, "%" PRId64, int64_value);
+ return tmp = t;
+ }
+ default:
+ return tmp = "";
+ }
+}
+
+bool field_value::get_asBool() const
+{
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ if (str_value == "True" || str_value == "true" || str_value == "1")
+ return true;
+ else
+ return false;
+ }
+ case ft_Boolean:
+ {
+ return bool_value;
+ }
+ case ft_Char:
+ {
+ if (char_value == 'T' || char_value == 't')
+ return true;
+ else
+ return false;
+ }
+ case ft_Short:
+ {
+ return (bool)short_value;
+ }
+ case ft_UShort:
+ {
+ return (bool)ushort_value;
+ }
+ case ft_Int:
+ {
+ return (bool)int_value;
+ }
+ case ft_UInt:
+ {
+ return (bool)uint_value;
+ }
+ case ft_Float:
+ {
+ return (bool)float_value;
+ }
+ case ft_Double:
+ {
+ return (bool)double_value;
+ }
+ case ft_Int64:
+ {
+ return (bool)int64_value;
+ }
+ default:
+ return false;
+ }
+}
+
+char field_value::get_asChar() const
+{
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ return str_value[0];
+ }
+ case ft_Boolean:
+ {
+ if (bool_value)
+ return 'T';
+ else
+ return 'F';
+ }
+ case ft_Char:
+ {
+ return char_value;
+ }
+ case ft_Short:
+ {
+ char t[10];
+ sprintf(t, "%i", short_value);
+ return t[0];
+ }
+ case ft_UShort:
+ {
+ char t[10];
+ sprintf(t, "%i", ushort_value);
+ return t[0];
+ }
+ case ft_Int:
+ {
+ char t[12];
+ sprintf(t, "%d", int_value);
+ return t[0];
+ }
+ case ft_UInt:
+ {
+ char t[12];
+ sprintf(t, "%u", uint_value);
+ return t[0];
+ }
+ case ft_Float:
+ {
+ char t[16];
+ sprintf(t, "%f", static_cast<double>(float_value));
+ return t[0];
+ }
+ case ft_Double:
+ {
+ char t[32];
+ sprintf(t, "%f", double_value);
+ return t[0];
+ }
+ case ft_Int64:
+ {
+ char t[24];
+ sprintf(t, "%" PRId64, int64_value);
+ return t[0];
+ }
+ default:
+ return '\0';
+ }
+}
+
+short field_value::get_asShort() const
+{
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ return (short)atoi(str_value.c_str());
+ }
+ case ft_Boolean:
+ {
+ return (short)bool_value;
+ }
+ case ft_Char:
+ {
+ return (short)char_value;
+ }
+ case ft_Short:
+ {
+ return short_value;
+ }
+ case ft_UShort:
+ {
+ return (short)ushort_value;
+ }
+ case ft_Int:
+ {
+ return (short)int_value;
+ }
+ case ft_UInt:
+ {
+ return (short)uint_value;
+ }
+ case ft_Float:
+ {
+ return (short)float_value;
+ }
+ case ft_Double:
+ {
+ return (short)double_value;
+ }
+ case ft_Int64:
+ {
+ return (short)int64_value;
+ }
+ default:
+ return 0;
+ }
+}
+
+unsigned short field_value::get_asUShort() const
+{
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ return (unsigned short)atoi(str_value.c_str());
+ }
+ case ft_Boolean:
+ {
+ return (unsigned short)bool_value;
+ }
+ case ft_Char:
+ {
+ return (unsigned short)char_value;
+ }
+ case ft_Short:
+ {
+ return (unsigned short)short_value;
+ }
+ case ft_UShort:
+ {
+ return ushort_value;
+ }
+ case ft_Int:
+ {
+ return (unsigned short)int_value;
+ }
+ case ft_UInt:
+ {
+ return (unsigned short)uint_value;
+ }
+ case ft_Float:
+ {
+ return (unsigned short)float_value;
+ }
+ case ft_Double:
+ {
+ return (unsigned short)double_value;
+ }
+ case ft_Int64:
+ {
+ return (unsigned short)int64_value;
+ }
+ default:
+ return 0;
+ }
+}
+
+int field_value::get_asInt() const
+{
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ return atoi(str_value.c_str());
+ }
+ case ft_Boolean:
+ {
+ return (int)bool_value;
+ }
+ case ft_Char:
+ {
+ return (int)char_value;
+ }
+ case ft_Short:
+ {
+ return (int)short_value;
+ }
+ case ft_UShort:
+ {
+ return (int)ushort_value;
+ }
+ case ft_Int:
+ {
+ return int_value;
+ }
+ case ft_UInt:
+ {
+ return (int)uint_value;
+ }
+ case ft_Float:
+ {
+ return (int)float_value;
+ }
+ case ft_Double:
+ {
+ return (int)double_value;
+ }
+ case ft_Int64:
+ {
+ return (int)int64_value;
+ }
+ default:
+ return 0;
+ }
+}
+
+unsigned int field_value::get_asUInt() const
+{
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ return (unsigned int)atoi(str_value.c_str());
+ }
+ case ft_Boolean:
+ {
+ return (unsigned int)bool_value;
+ }
+ case ft_Char:
+ {
+ return (unsigned int)char_value;
+ }
+ case ft_Short:
+ {
+ return (unsigned int)short_value;
+ }
+ case ft_UShort:
+ {
+ return (unsigned int)ushort_value;
+ }
+ case ft_Int:
+ {
+ return (unsigned int)int_value;
+ }
+ case ft_UInt:
+ {
+ return uint_value;
+ }
+ case ft_Float:
+ {
+ return (unsigned int)float_value;
+ }
+ case ft_Double:
+ {
+ return (unsigned int)double_value;
+ }
+ case ft_Int64:
+ {
+ return (unsigned int)int64_value;
+ }
+ default:
+ return 0;
+ }
+}
+
+float field_value::get_asFloat() const
+{
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ return (float)atof(str_value.c_str());
+ }
+ case ft_Boolean:
+ {
+ return (float)bool_value;
+ }
+ case ft_Char:
+ {
+ return (float)char_value;
+ }
+ case ft_Short:
+ {
+ return (float)short_value;
+ }
+ case ft_UShort:
+ {
+ return (float)ushort_value;
+ }
+ case ft_Int:
+ {
+ return (float)int_value;
+ }
+ case ft_UInt:
+ {
+ return (float)uint_value;
+ }
+ case ft_Float:
+ {
+ return float_value;
+ }
+ case ft_Double:
+ {
+ return (float)double_value;
+ }
+ case ft_Int64:
+ {
+ return (float)int64_value;
+ }
+ default:
+ return 0.0;
+ }
+}
+
+double field_value::get_asDouble() const
+{
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ return atof(str_value.c_str());
+ }
+ case ft_Boolean:
+ {
+ return (double)bool_value;
+ }
+ case ft_Char:
+ {
+ return (double)char_value;
+ }
+ case ft_Short:
+ {
+ return (double)short_value;
+ }
+ case ft_UShort:
+ {
+ return (double)ushort_value;
+ }
+ case ft_Int:
+ {
+ return (double)int_value;
+ }
+ case ft_UInt:
+ {
+ return (double)uint_value;
+ }
+ case ft_Float:
+ {
+ return (double)float_value;
+ }
+ case ft_Double:
+ {
+ return (double)double_value;
+ }
+ case ft_Int64:
+ {
+ return (double)int64_value;
+ }
+ default:
+ return 0.0;
+ }
+}
+
+int64_t field_value::get_asInt64() const
+{
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ return std::atoll(str_value.c_str());
+ }
+ case ft_Boolean:
+ {
+ return (int64_t)bool_value;
+ }
+ case ft_Char:
+ {
+ return (int64_t)char_value;
+ }
+ case ft_Short:
+ {
+ return (int64_t)short_value;
+ }
+ case ft_UShort:
+ {
+ return (int64_t)ushort_value;
+ }
+ case ft_Int:
+ {
+ return (int64_t)int_value;
+ }
+ case ft_UInt:
+ {
+ return (int64_t)uint_value;
+ }
+ case ft_Float:
+ {
+ return (int64_t)float_value;
+ }
+ case ft_Double:
+ {
+ return (int64_t)double_value;
+ }
+ case ft_Int64:
+ {
+ return int64_value;
+ }
+ default:
+ return 0;
+ }
+}
+
+field_value& field_value::operator=(const field_value& fv)
+{
+ if (this == &fv)
+ return *this;
+
+ is_null = fv.get_isNull();
+
+ switch (fv.get_fType())
+ {
+ case ft_String:
+ {
+ set_asString(fv.get_asString());
+ return *this;
+ break;
+ }
+ case ft_Boolean:
+ {
+ set_asBool(fv.get_asBool());
+ return *this;
+ break;
+ }
+ case ft_Char:
+ {
+ set_asChar(fv.get_asChar());
+ return *this;
+ break;
+ }
+ case ft_Short:
+ {
+ set_asShort(fv.get_asShort());
+ return *this;
+ break;
+ }
+ case ft_UShort:
+ {
+ set_asUShort(fv.get_asUShort());
+ return *this;
+ break;
+ }
+ case ft_Int:
+ {
+ set_asInt(fv.get_asInt());
+ return *this;
+ break;
+ }
+ case ft_UInt:
+ {
+ set_asUInt(fv.get_asUInt());
+ return *this;
+ break;
+ }
+ case ft_Float:
+ {
+ set_asFloat(fv.get_asFloat());
+ return *this;
+ break;
+ }
+ case ft_Double:
+ {
+ set_asDouble(fv.get_asDouble());
+ return *this;
+ break;
+ }
+ case ft_Int64:
+ {
+ set_asInt64(fv.get_asInt64());
+ return *this;
+ break;
+ }
+ default:
+ return *this;
+ }
+}
+
+//Set functions
+void field_value::set_asString(const char* s)
+{
+ str_value = s;
+ field_type = ft_String;
+}
+
+void field_value::set_asString(const std::string& s)
+{
+ str_value = s;
+ field_type = ft_String;
+}
+
+void field_value::set_asBool(const bool b)
+{
+ bool_value = b;
+ field_type = ft_Boolean;
+}
+
+void field_value::set_asChar(const char c)
+{
+ char_value = c;
+ field_type = ft_Char;
+}
+
+void field_value::set_asShort(const short s)
+{
+ short_value = s;
+ field_type = ft_Short;
+}
+
+void field_value::set_asUShort(const unsigned short us)
+{
+ ushort_value = us;
+ field_type = ft_UShort;
+}
+
+void field_value::set_asInt(const int i)
+{
+ int_value = i;
+ field_type = ft_Int;
+}
+
+void field_value::set_asUInt(const unsigned int ui)
+{
+ int_value = ui;
+ field_type = ft_UInt;
+}
+
+void field_value::set_asFloat(const float f)
+{
+ float_value = f;
+ field_type = ft_Float;
+}
+
+void field_value::set_asDouble(const double d)
+{
+ double_value = d;
+ field_type = ft_Double;
+}
+
+void field_value::set_asInt64(const int64_t i)
+{
+ int64_value = i;
+ field_type = ft_Int64;
+}
+
+fType field_value::get_field_type()
+{
+ return field_type;
+}
+
+std::string field_value::gft()
+{
+ std::string tmp;
+ switch (field_type)
+ {
+ case ft_String:
+ {
+ tmp.assign("string");
+ return tmp;
+ }
+ case ft_Boolean:
+ {
+ tmp.assign("bool");
+ return tmp;
+ }
+ case ft_Char:
+ {
+ tmp.assign("char");
+ return tmp;
+ }
+ case ft_Short:
+ {
+ tmp.assign("short");
+ return tmp;
+ }
+ case ft_Int:
+ {
+ tmp.assign("int");
+ return tmp;
+ }
+ case ft_Float:
+ {
+ tmp.assign("float");
+ return tmp;
+ }
+ case ft_Double:
+ {
+ tmp.assign("double");
+ return tmp;
+ }
+ case ft_Int64:
+ {
+ tmp.assign("int64");
+ return tmp;
+ }
+ default:
+ break;
+ }
+
+ return tmp;
+}
+
+} // namespace dbiplus
diff --git a/xbmc/dbwrappers/qry_dat.h b/xbmc/dbwrappers/qry_dat.h
new file mode 100644
index 0000000..150de2d
--- /dev/null
+++ b/xbmc/dbwrappers/qry_dat.h
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2004, Leo Seib, Hannover
+ *
+ * Project:Dataset C++ Dynamic Library
+ * Module: FieldValue class and result sets classes header file
+ * Author: Leo Seib E-Mail: leoseib@web.de
+ * Begin: 5/04/2002
+ *
+ * SPDX-License-Identifier: MIT
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <iostream>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace dbiplus
+{
+
+enum fType
+{
+ ft_String,
+ ft_Boolean,
+ ft_Char,
+ ft_WChar,
+ ft_WideString,
+ ft_Short,
+ ft_UShort,
+ ft_Int,
+ ft_UInt,
+ ft_Float,
+ ft_Double,
+ ft_LongDouble,
+ ft_Int64,
+ ft_Object
+};
+
+#ifdef TARGET_WINDOWS_STORE
+#pragma pack(push)
+#pragma pack(8)
+#endif
+
+class field_value
+{
+private:
+ fType field_type;
+ std::string str_value;
+ union
+ {
+ bool bool_value;
+ char char_value;
+ short short_value;
+ unsigned short ushort_value;
+ int int_value;
+ unsigned int uint_value;
+ float float_value;
+ double double_value;
+ int64_t int64_value;
+ void* object_value;
+ };
+
+ bool is_null;
+
+public:
+ field_value();
+ explicit field_value(const char* s);
+ explicit field_value(const bool b);
+ explicit field_value(const char c);
+ explicit field_value(const short s);
+ explicit field_value(const unsigned short us);
+ explicit field_value(const int l);
+ explicit field_value(const unsigned int ul);
+ explicit field_value(const float f);
+ explicit field_value(const double d);
+ explicit field_value(const int64_t i);
+ field_value(const field_value& fv);
+ ~field_value();
+
+ fType get_fType() const { return field_type; }
+ bool get_isNull() const { return is_null; }
+ std::string get_asString() const;
+ bool get_asBool() const;
+ char get_asChar() const;
+ short get_asShort() const;
+ unsigned short get_asUShort() const;
+ int get_asInt() const;
+ unsigned int get_asUInt() const;
+ float get_asFloat() const;
+ double get_asDouble() const;
+ int64_t get_asInt64() const;
+
+ field_value& operator=(const char* s)
+ {
+ set_asString(s);
+ return *this;
+ }
+ field_value& operator=(const std::string& s)
+ {
+ set_asString(s);
+ return *this;
+ }
+ field_value& operator=(const bool b)
+ {
+ set_asBool(b);
+ return *this;
+ }
+ field_value& operator=(const short s)
+ {
+ set_asShort(s);
+ return *this;
+ }
+ field_value& operator=(const unsigned short us)
+ {
+ set_asUShort(us);
+ return *this;
+ }
+ field_value& operator=(const int l)
+ {
+ set_asInt(l);
+ return *this;
+ }
+ field_value& operator=(const unsigned int l)
+ {
+ set_asUInt(l);
+ return *this;
+ }
+ field_value& operator=(const float f)
+ {
+ set_asFloat(f);
+ return *this;
+ }
+ field_value& operator=(const double d)
+ {
+ set_asDouble(d);
+ return *this;
+ }
+ field_value& operator=(const int64_t i)
+ {
+ set_asInt64(i);
+ return *this;
+ }
+ field_value& operator=(const field_value& fv);
+
+ //class ostream;
+ friend std::ostream& operator<<(std::ostream& os, const field_value& fv)
+ {
+ switch (fv.get_fType())
+ {
+ case ft_String:
+ {
+ return os << fv.get_asString();
+ break;
+ }
+ case ft_Boolean:
+ {
+ return os << fv.get_asBool();
+ break;
+ }
+ case ft_Char:
+ {
+ return os << fv.get_asChar();
+ break;
+ }
+ case ft_Short:
+ {
+ return os << fv.get_asShort();
+ break;
+ }
+ case ft_UShort:
+ {
+ return os << fv.get_asUShort();
+ break;
+ }
+ case ft_Int:
+ {
+ return os << fv.get_asInt();
+ break;
+ }
+ case ft_UInt:
+ {
+ return os << fv.get_asUInt();
+ break;
+ }
+ case ft_Float:
+ {
+ return os << fv.get_asFloat();
+ break;
+ }
+ case ft_Double:
+ {
+ return os << fv.get_asDouble();
+ break;
+ }
+ case ft_Int64:
+ {
+ return os << fv.get_asInt64();
+ break;
+ }
+ default:
+ {
+ return os;
+ break;
+ }
+ }
+ }
+
+ void set_isNull() { is_null = true; }
+ void set_asString(const char* s);
+ void set_asString(const std::string& s);
+ void set_asBool(const bool b);
+ void set_asChar(const char c);
+ void set_asShort(const short s);
+ void set_asUShort(const unsigned short us);
+ void set_asInt(const int l);
+ void set_asUInt(const unsigned int l);
+ void set_asFloat(const float f);
+ void set_asDouble(const double d);
+ void set_asInt64(const int64_t i);
+
+ fType get_field_type();
+ std::string gft();
+};
+
+struct field_prop
+{
+ std::string name;
+};
+
+struct field
+{
+ field_prop props;
+ field_value val;
+};
+
+typedef std::vector<field> Fields;
+typedef std::vector<field_value> sql_record;
+typedef std::vector<field_prop> record_prop;
+typedef std::vector<sql_record*> query_data;
+typedef field_value variant;
+
+class result_set
+{
+public:
+ result_set() = default;
+ ~result_set() { clear(); };
+ void clear()
+ {
+ for (unsigned int i = 0; i < records.size(); i++)
+ if (records[i])
+ delete records[i];
+ records.clear();
+ record_header.clear();
+ };
+
+ record_prop record_header;
+ query_data records;
+};
+
+#ifdef TARGET_WINDOWS_STORE
+#pragma pack(pop)
+#endif
+} // namespace dbiplus
diff --git a/xbmc/dbwrappers/sqlitedataset.cpp b/xbmc/dbwrappers/sqlitedataset.cpp
new file mode 100644
index 0000000..ef9c927
--- /dev/null
+++ b/xbmc/dbwrappers/sqlitedataset.cpp
@@ -0,0 +1,1063 @@
+/**********************************************************************
+ * Copyright (C) 2004, Leo Seib, Hannover
+ *
+ * Project:SQLiteDataset C++ Dynamic Library
+ * Module: SQLiteDataset class realisation file
+ * Author: Leo Seib E-Mail: leoseib@web.de
+ * Begin: 5/04/2002
+ *
+ * SPDX-License-Identifier: MIT
+ * See LICENSES/README.md for more information.
+ */
+
+#include "sqlitedataset.h"
+
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+using namespace std::chrono_literals;
+
+namespace
+{
+#define X(VAL) std::make_pair(VAL, #VAL)
+//!@todo Remove ifdefs when sqlite version requirement has been bumped to at least 3.26.0
+const std::map<int, const char*> g_SqliteErrorStrings = {
+ X(SQLITE_OK),
+ X(SQLITE_ERROR),
+ X(SQLITE_INTERNAL),
+ X(SQLITE_PERM),
+ X(SQLITE_ABORT),
+ X(SQLITE_BUSY),
+ X(SQLITE_LOCKED),
+ X(SQLITE_NOMEM),
+ X(SQLITE_READONLY),
+ X(SQLITE_INTERRUPT),
+ X(SQLITE_IOERR),
+ X(SQLITE_CORRUPT),
+ X(SQLITE_NOTFOUND),
+ X(SQLITE_FULL),
+ X(SQLITE_CANTOPEN),
+ X(SQLITE_PROTOCOL),
+ X(SQLITE_EMPTY),
+ X(SQLITE_SCHEMA),
+ X(SQLITE_TOOBIG),
+ X(SQLITE_CONSTRAINT),
+ X(SQLITE_MISMATCH),
+ X(SQLITE_MISUSE),
+ X(SQLITE_NOLFS),
+ X(SQLITE_AUTH),
+ X(SQLITE_FORMAT),
+ X(SQLITE_RANGE),
+ X(SQLITE_NOTADB),
+ X(SQLITE_NOTICE),
+ X(SQLITE_WARNING),
+ X(SQLITE_ROW),
+ X(SQLITE_DONE),
+#if defined(SQLITE_ERROR_MISSING_COLLSEQ)
+ X(SQLITE_ERROR_MISSING_COLLSEQ),
+#endif
+#if defined(SQLITE_ERROR_RETRY)
+ X(SQLITE_ERROR_RETRY),
+#endif
+#if defined(SQLITE_ERROR_SNAPSHOT)
+ X(SQLITE_ERROR_SNAPSHOT),
+#endif
+ X(SQLITE_IOERR_READ),
+ X(SQLITE_IOERR_SHORT_READ),
+ X(SQLITE_IOERR_WRITE),
+ X(SQLITE_IOERR_FSYNC),
+ X(SQLITE_IOERR_DIR_FSYNC),
+ X(SQLITE_IOERR_TRUNCATE),
+ X(SQLITE_IOERR_FSTAT),
+ X(SQLITE_IOERR_UNLOCK),
+ X(SQLITE_IOERR_RDLOCK),
+ X(SQLITE_IOERR_DELETE),
+ X(SQLITE_IOERR_BLOCKED),
+ X(SQLITE_IOERR_NOMEM),
+ X(SQLITE_IOERR_ACCESS),
+ X(SQLITE_IOERR_CHECKRESERVEDLOCK),
+ X(SQLITE_IOERR_LOCK),
+ X(SQLITE_IOERR_CLOSE),
+ X(SQLITE_IOERR_DIR_CLOSE),
+ X(SQLITE_IOERR_SHMOPEN),
+ X(SQLITE_IOERR_SHMSIZE),
+ X(SQLITE_IOERR_SHMLOCK),
+ X(SQLITE_IOERR_SHMMAP),
+ X(SQLITE_IOERR_SEEK),
+ X(SQLITE_IOERR_DELETE_NOENT),
+ X(SQLITE_IOERR_MMAP),
+ X(SQLITE_IOERR_GETTEMPPATH),
+ X(SQLITE_IOERR_CONVPATH),
+#if defined(SQLITE_IOERR_VNODE)
+ X(SQLITE_IOERR_VNODE),
+#endif
+#if defined(SQLITE_IOERR_AUTH)
+ X(SQLITE_IOERR_AUTH),
+#endif
+#if defined(SQLITE_IOERR_BEGIN_ATOMIC)
+ X(SQLITE_IOERR_BEGIN_ATOMIC),
+#endif
+#if defined(SQLITE_IOERR_COMMIT_ATOMIC)
+ X(SQLITE_IOERR_COMMIT_ATOMIC),
+#endif
+#if defined(SQLITE_IOERR_ROLLBACK_ATOMIC)
+ X(SQLITE_IOERR_ROLLBACK_ATOMIC),
+#endif
+ X(SQLITE_LOCKED_SHAREDCACHE),
+#if defined(SQLITE_LOCKED_VTAB)
+ X(SQLITE_LOCKED_VTAB),
+#endif
+ X(SQLITE_BUSY_RECOVERY),
+ X(SQLITE_BUSY_SNAPSHOT),
+ X(SQLITE_CANTOPEN_NOTEMPDIR),
+ X(SQLITE_CANTOPEN_ISDIR),
+ X(SQLITE_CANTOPEN_FULLPATH),
+ X(SQLITE_CANTOPEN_CONVPATH),
+#if defined(SQLITE_CANTOPEN_DIRTYWAL)
+ X(SQLITE_CANTOPEN_DIRTYWAL),
+#endif
+ X(SQLITE_CORRUPT_VTAB),
+#if defined(SQLITE_CORRUPT_SEQUENCE)
+ X(SQLITE_CORRUPT_SEQUENCE),
+#endif
+ X(SQLITE_READONLY_RECOVERY),
+ X(SQLITE_READONLY_CANTLOCK),
+ X(SQLITE_READONLY_ROLLBACK),
+ X(SQLITE_READONLY_DBMOVED),
+#if defined(SQLITE_READONLY_CANTINIT)
+ X(SQLITE_READONLY_CANTINIT),
+#endif
+#if defined(SQLITE_READONLY_DIRECTORY)
+ X(SQLITE_READONLY_DIRECTORY),
+#endif
+ X(SQLITE_ABORT_ROLLBACK),
+ X(SQLITE_CONSTRAINT_CHECK),
+ X(SQLITE_CONSTRAINT_COMMITHOOK),
+ X(SQLITE_CONSTRAINT_FOREIGNKEY),
+ X(SQLITE_CONSTRAINT_FUNCTION),
+ X(SQLITE_CONSTRAINT_NOTNULL),
+ X(SQLITE_CONSTRAINT_PRIMARYKEY),
+ X(SQLITE_CONSTRAINT_TRIGGER),
+ X(SQLITE_CONSTRAINT_UNIQUE),
+ X(SQLITE_CONSTRAINT_VTAB),
+ X(SQLITE_CONSTRAINT_ROWID),
+ X(SQLITE_NOTICE_RECOVER_WAL),
+ X(SQLITE_NOTICE_RECOVER_ROLLBACK),
+ X(SQLITE_WARNING_AUTOINDEX),
+ X(SQLITE_AUTH_USER),
+#if defined(SQLITE_OK_LOAD_PERMANENTLY)
+ X(SQLITE_OK_LOAD_PERMANENTLY),
+#endif
+};
+#undef X
+} // namespace
+
+namespace dbiplus
+{
+//************* Callback function ***************************
+
+int callback(void* res_ptr, int ncol, char** result, char** cols)
+{
+ result_set* r = static_cast<result_set*>(res_ptr);
+
+ if (!r->record_header.size())
+ {
+ r->record_header.reserve(ncol);
+ for (int i = 0; i < ncol; i++)
+ {
+ field_prop header;
+ header.name = cols[i];
+ r->record_header.push_back(header);
+ }
+ }
+
+ if (result != NULL)
+ {
+ sql_record* rec = new sql_record;
+ rec->resize(ncol);
+ for (int i = 0; i < ncol; i++)
+ {
+ field_value& v = rec->at(i);
+ if (result[i] == NULL)
+ {
+ v.set_asString("");
+ v.set_isNull();
+ }
+ else
+ {
+ v.set_asString(result[i]);
+ }
+ }
+ r->records.push_back(rec);
+ }
+ return 0;
+}
+
+static int busy_callback(void*, int busyCount)
+{
+ KODI::TIME::Sleep(100ms);
+ return 1;
+}
+
+//************* SqliteDatabase implementation ***************
+
+SqliteDatabase::SqliteDatabase()
+{
+
+ active = false;
+ _in_transaction = false; // for transaction
+
+ error = "Unknown database error"; //S_NO_CONNECTION;
+ host = "localhost";
+ port = "";
+ db = "sqlite.db";
+ login = "root";
+ passwd = "";
+}
+
+SqliteDatabase::~SqliteDatabase()
+{
+ disconnect();
+}
+
+Dataset* SqliteDatabase::CreateDataset() const
+{
+ return new SqliteDataset(const_cast<SqliteDatabase*>(this));
+}
+
+void SqliteDatabase::setHostName(const char* newHost)
+{
+ host = newHost;
+
+ // hostname is the relative folder to the database, ensure it's slash terminated
+ if (host[host.length() - 1] != '/' && host[host.length() - 1] != '\\')
+ host += "/";
+
+ // ensure the fully qualified path has slashes in the correct direction
+ if ((host[1] == ':') && isalpha(host[0]))
+ {
+ size_t pos = 0;
+ while ((pos = host.find('/', pos)) != std::string::npos)
+ host.replace(pos++, 1, "\\");
+ }
+ else
+ {
+ size_t pos = 0;
+ while ((pos = host.find('\\', pos)) != std::string::npos)
+ host.replace(pos++, 1, "/");
+ }
+}
+
+void SqliteDatabase::setDatabase(const char* newDb)
+{
+ db = newDb;
+
+ // db is the filename for the database, ensure it's not slash prefixed
+ if (newDb[0] == '/' || newDb[0] == '\\')
+ db = db.substr(1);
+
+ // ensure the ".db" extension is appended to the end
+ if (db.find(".db") != (db.length() - 3))
+ db += ".db";
+}
+
+int SqliteDatabase::status(void)
+{
+ if (active == false)
+ return DB_CONNECTION_NONE;
+ return DB_CONNECTION_OK;
+}
+
+int SqliteDatabase::setErr(int err_code, const char* qry)
+{
+ std::stringstream ss;
+ ss << "[" << db << "] ";
+ auto errorIt = g_SqliteErrorStrings.find(err_code);
+ if (errorIt != g_SqliteErrorStrings.end())
+ {
+ ss << "SQLite error " << errorIt->second;
+ }
+ else
+ {
+ ss << "Undefined SQLite error " << err_code;
+ }
+ if (conn)
+ ss << " (" << sqlite3_errmsg(conn) << ")";
+ ss << "\nQuery: " << qry;
+ error = ss.str();
+ return err_code;
+}
+
+const char* SqliteDatabase::getErrorMsg()
+{
+ return error.c_str();
+}
+
+static int AlphaNumericCollation(
+ void* not_used, int nKey1, const void* pKey1, int nKey2, const void* pKey2)
+{
+ return StringUtils::AlphaNumericCollation(nKey1, pKey1, nKey2, pKey2);
+}
+
+int SqliteDatabase::connect(bool create)
+{
+ if (host.empty() || db.empty())
+ return DB_CONNECTION_NONE;
+
+ //CLog::Log(LOGDEBUG, "Connecting to sqlite:{}:{}", host, db);
+
+ std::string db_fullpath = URIUtils::AddFileToFolder(host, db);
+
+ try
+ {
+ disconnect();
+ int flags = SQLITE_OPEN_READWRITE;
+ if (create)
+ flags |= SQLITE_OPEN_CREATE;
+ int errorCode = sqlite3_open_v2(db_fullpath.c_str(), &conn, flags, NULL);
+ if (create && errorCode == SQLITE_CANTOPEN)
+ {
+ CLog::Log(LOGFATAL, "SqliteDatabase: can't open {}", db_fullpath);
+ throw std::runtime_error("SqliteDatabase: can't open " + db_fullpath);
+ }
+ else if (errorCode == SQLITE_OK)
+ {
+ sqlite3_extended_result_codes(conn, 1);
+ sqlite3_busy_handler(conn, busy_callback, NULL);
+ if (setErr(sqlite3_exec(getHandle(), "PRAGMA empty_result_callbacks=ON", NULL, NULL, NULL),
+ "PRAGMA empty_result_callbacks=ON") != SQLITE_OK)
+ {
+ throw DbErrors("%s", getErrorMsg());
+ }
+ else if (sqlite3_db_readonly(conn, nullptr) == 1)
+ {
+ CLog::Log(LOGFATAL, "SqliteDatabase: {} is read only", db_fullpath);
+ throw std::runtime_error("SqliteDatabase: " + db_fullpath + " is read only");
+ }
+ errorCode = sqlite3_create_collation(conn, "ALPHANUM", SQLITE_UTF8, 0, AlphaNumericCollation);
+ if (errorCode != SQLITE_OK)
+ {
+ CLog::Log(LOGFATAL, "SqliteDatabase: can not register collation");
+ throw std::runtime_error("SqliteDatabase: can not register collation " + db_fullpath);
+ }
+ active = true;
+ return DB_CONNECTION_OK;
+ }
+ }
+ catch (const DbErrors&)
+ {
+ }
+
+ sqlite3_close(conn);
+
+ return DB_CONNECTION_NONE;
+}
+
+bool SqliteDatabase::exists(void)
+{
+ bool bRet = false;
+ if (!active)
+ return bRet;
+ result_set res;
+ char sqlcmd[512];
+
+ // performing a select all on the sqlite_master will return rows if there are tables
+ // defined indicating it's not empty and therefore must "exist".
+ sprintf(sqlcmd, "SELECT * FROM sqlite_master");
+ if ((last_err = sqlite3_exec(getHandle(), sqlcmd, &callback, &res, NULL)) == SQLITE_OK)
+ {
+ bRet = (res.records.size() > 0);
+ }
+
+ return bRet;
+}
+
+void SqliteDatabase::disconnect(void)
+{
+ if (active == false)
+ return;
+ sqlite3_close(conn);
+ active = false;
+}
+
+int SqliteDatabase::create()
+{
+ return connect(true);
+}
+
+int SqliteDatabase::copy(const char* backup_name)
+{
+ if (active == false)
+ throw DbErrors("Can't copy database: no active connection...");
+
+ CLog::Log(LOGDEBUG, "Copying from {} to {} at {}", db, backup_name, host);
+
+ int rc;
+ std::string backup_db = backup_name;
+
+ sqlite3* pFile; /* Database connection opened on zFilename */
+ sqlite3_backup* pBackup; /* Backup object used to copy data */
+
+ //
+ if (backup_name[0] == '/' || backup_name[0] == '\\')
+ backup_db = backup_db.substr(1);
+
+ // ensure the ".db" extension is appended to the end
+ if (backup_db.find(".db") != (backup_db.length() - 3))
+ backup_db += ".db";
+
+ std::string backup_path = host + backup_db;
+
+ /* Open the database file identified by zFilename. Exit early if this fails
+ ** for any reason. */
+ rc = sqlite3_open(backup_path.c_str(), &pFile);
+ if (rc == SQLITE_OK)
+ {
+ pBackup = sqlite3_backup_init(pFile, "main", getHandle(), "main");
+
+ if (pBackup)
+ {
+ (void)sqlite3_backup_step(pBackup, -1);
+ (void)sqlite3_backup_finish(pBackup);
+ }
+
+ rc = sqlite3_errcode(pFile);
+ }
+
+ (void)sqlite3_close(pFile);
+
+ if (rc != SQLITE_OK)
+ throw DbErrors("Can't copy database. (%d)", rc);
+
+ return rc;
+}
+
+int SqliteDatabase::drop_analytics(void)
+{
+ // SqliteDatabase::copy used a full database copy, so we have a new version
+ // with all the analytics stuff. We should clean database from everything but data
+ if (active == false)
+ throw DbErrors("Can't drop extras database: no active connection...");
+
+ char sqlcmd[4096];
+ result_set res;
+
+ CLog::Log(LOGDEBUG, "Cleaning indexes from database {} at {}", db, host);
+ sprintf(sqlcmd, "SELECT name FROM sqlite_master WHERE type == 'index' AND sql IS NOT NULL");
+ if ((last_err = sqlite3_exec(conn, sqlcmd, &callback, &res, NULL)) != SQLITE_OK)
+ return DB_UNEXPECTED_RESULT;
+
+ for (size_t i = 0; i < res.records.size(); i++)
+ {
+ sprintf(sqlcmd, "DROP INDEX '%s'", res.records[i]->at(0).get_asString().c_str());
+ if ((last_err = sqlite3_exec(conn, sqlcmd, NULL, NULL, NULL)) != SQLITE_OK)
+ return DB_UNEXPECTED_RESULT;
+ }
+ res.clear();
+
+ CLog::Log(LOGDEBUG, "Cleaning views from database {} at {}", db, host);
+ sprintf(sqlcmd, "SELECT name FROM sqlite_master WHERE type == 'view'");
+ if ((last_err = sqlite3_exec(conn, sqlcmd, &callback, &res, NULL)) != SQLITE_OK)
+ return DB_UNEXPECTED_RESULT;
+
+ for (size_t i = 0; i < res.records.size(); i++)
+ {
+ sprintf(sqlcmd, "DROP VIEW '%s'", res.records[i]->at(0).get_asString().c_str());
+ if ((last_err = sqlite3_exec(conn, sqlcmd, NULL, NULL, NULL)) != SQLITE_OK)
+ return DB_UNEXPECTED_RESULT;
+ }
+ res.clear();
+
+ CLog::Log(LOGDEBUG, "Cleaning triggers from database {} at {}", db, host);
+ sprintf(sqlcmd, "SELECT name FROM sqlite_master WHERE type == 'trigger'");
+ if ((last_err = sqlite3_exec(conn, sqlcmd, &callback, &res, NULL)) != SQLITE_OK)
+ return DB_UNEXPECTED_RESULT;
+
+ for (size_t i = 0; i < res.records.size(); i++)
+ {
+ sprintf(sqlcmd, "DROP TRIGGER '%s'", res.records[i]->at(0).get_asString().c_str());
+ if ((last_err = sqlite3_exec(conn, sqlcmd, NULL, NULL, NULL)) != SQLITE_OK)
+ return DB_UNEXPECTED_RESULT;
+ }
+ // res would be cleared on destruct
+
+ return DB_COMMAND_OK;
+}
+
+int SqliteDatabase::drop()
+{
+ if (active == false)
+ throw DbErrors("Can't drop database: no active connection...");
+ disconnect();
+ if (!unlink(db.c_str()))
+ {
+ throw DbErrors("Can't drop database: can't unlink the file %s,\nError: %s", db.c_str(),
+ strerror(errno));
+ }
+ return DB_COMMAND_OK;
+}
+
+long SqliteDatabase::nextid(const char* sname)
+{
+ if (!active)
+ return DB_UNEXPECTED_RESULT;
+ int id; /*,nrow,ncol;*/
+ result_set res;
+ char sqlcmd[512];
+ snprintf(sqlcmd, sizeof(sqlcmd), "SELECT nextid FROM %s WHERE seq_name = '%s'",
+ sequence_table.c_str(), sname);
+ if ((last_err = sqlite3_exec(getHandle(), sqlcmd, &callback, &res, NULL)) != SQLITE_OK)
+ {
+ return DB_UNEXPECTED_RESULT;
+ }
+ if (res.records.empty())
+ {
+ id = 1;
+ snprintf(sqlcmd, sizeof(sqlcmd), "INSERT INTO %s (nextid,seq_name) VALUES (%d,'%s')",
+ sequence_table.c_str(), id, sname);
+ if ((last_err = sqlite3_exec(conn, sqlcmd, NULL, NULL, NULL)) != SQLITE_OK)
+ return DB_UNEXPECTED_RESULT;
+ return id;
+ }
+ else
+ {
+ id = res.records[0]->at(0).get_asInt() + 1;
+ snprintf(sqlcmd, sizeof(sqlcmd), "UPDATE %s SET nextid=%d WHERE seq_name = '%s'",
+ sequence_table.c_str(), id, sname);
+ if ((last_err = sqlite3_exec(conn, sqlcmd, NULL, NULL, NULL)) != SQLITE_OK)
+ return DB_UNEXPECTED_RESULT;
+ return id;
+ }
+ return DB_UNEXPECTED_RESULT;
+}
+
+// methods for transactions
+// ---------------------------------------------
+void SqliteDatabase::start_transaction()
+{
+ if (active)
+ {
+ sqlite3_exec(conn, "begin IMMEDIATE", NULL, NULL, NULL);
+ _in_transaction = true;
+ }
+}
+
+void SqliteDatabase::commit_transaction()
+{
+ if (active)
+ {
+ sqlite3_exec(conn, "commit", NULL, NULL, NULL);
+ _in_transaction = false;
+ }
+}
+
+void SqliteDatabase::rollback_transaction()
+{
+ if (active)
+ {
+ sqlite3_exec(conn, "rollback", NULL, NULL, NULL);
+ _in_transaction = false;
+ }
+}
+
+// methods for formatting
+// ---------------------------------------------
+std::string SqliteDatabase::vprepare(const char* format, va_list args)
+{
+ std::string strFormat = format;
+ std::string strResult = "";
+ char* p;
+ size_t pos;
+
+ // %q is the sqlite format string for %s.
+ // Any bad character, like "'", will be replaced with a proper one
+ pos = 0;
+ while ((pos = strFormat.find("%s", pos)) != std::string::npos)
+ strFormat.replace(pos++, 2, "%q");
+
+ // the %I64 enhancement is not supported by sqlite3_vmprintf
+ // must be %ll instead
+ pos = 0;
+ while ((pos = strFormat.find("%I64", pos)) != std::string::npos)
+ strFormat.replace(pos++, 4, "%ll");
+
+ p = sqlite3_vmprintf(strFormat.c_str(), args);
+ if (p)
+ {
+ strResult = p;
+ sqlite3_free(p);
+ }
+
+ // Strip SEPARATOR from all GROUP_CONCAT statements:
+ // before: GROUP_CONCAT(field SEPARATOR '; ')
+ // after: GROUP_CONCAT(field, '; ')
+ // Can not specify separator when have DISTINCT, comma used by default
+ pos = strResult.find("GROUP_CONCAT(");
+ while (pos != std::string::npos)
+ {
+ size_t pos2 = strResult.find(" SEPARATOR ", pos + 1);
+ if (pos2 != std::string::npos)
+ strResult.replace(pos2, 10, ",");
+ pos = strResult.find("GROUP_CONCAT(", pos + 1);
+ }
+ // Replace CONCAT with || to concatenate text fields:
+ // before: CONCAT(field1, field2, field3)
+ // after: field1 || field2 || field3
+ // Avoid commas in substatements and within single quotes
+ // before: CONCAT(field1, ',', REPLACE(field2, ',', '-'), field3)
+ // after: field1 || ',' || REPLACE(field2, ',', '-') || field3
+ pos = strResult.find("CONCAT(");
+ while (pos != std::string::npos)
+ {
+ if (pos == 0 || strResult[pos - 1] == ' ') // Not GROUP_CONCAT
+ {
+ // Check each char for other bracket or single quote pairs
+ unsigned int brackets = 1;
+ bool quoted = false;
+ size_t index = pos + 7; // start after "CONCAT("
+ while (index < strResult.size() && brackets != 0)
+ {
+ if (strResult[index] == '(')
+ brackets++;
+ else if (strResult[index] == ')')
+ {
+ brackets--;
+ if (brackets == 0)
+ strResult.erase(index, 1); //Remove closing bracket of CONCAT
+ }
+ else if (strResult[index] == '\'')
+ quoted = !quoted;
+ else if (strResult[index] == ',' && brackets == 1 && !quoted)
+ strResult.replace(index, 1, "||");
+ index++;
+ }
+ strResult.erase(pos, 7); //Remove "CONCAT("
+ }
+ pos = strResult.find("CONCAT(", pos + 1);
+ }
+
+ return strResult;
+}
+
+//************* SqliteDataset implementation ***************
+
+SqliteDataset::SqliteDataset() : Dataset()
+{
+ haveError = false;
+ db = NULL;
+ autorefresh = false;
+}
+
+SqliteDataset::SqliteDataset(SqliteDatabase* newDb) : Dataset(newDb)
+{
+ haveError = false;
+ db = newDb;
+ autorefresh = false;
+}
+
+SqliteDataset::~SqliteDataset()
+{
+}
+
+void SqliteDataset::set_autorefresh(bool val)
+{
+ autorefresh = val;
+}
+
+//--------- protected functions implementation -----------------//
+
+sqlite3* SqliteDataset::handle()
+{
+ if (db != NULL)
+ {
+ return static_cast<SqliteDatabase*>(db)->getHandle();
+ }
+ else
+ return NULL;
+}
+
+void SqliteDataset::make_query(StringList& _sql)
+{
+ std::string query;
+ if (db == NULL)
+ throw DbErrors("No Database Connection");
+
+ try
+ {
+
+ if (autocommit)
+ db->start_transaction();
+
+ for (const std::string& i : _sql)
+ {
+ query = i;
+ char* err = NULL;
+ Dataset::parse_sql(query);
+ if (db->setErr(sqlite3_exec(this->handle(), query.c_str(), NULL, NULL, &err),
+ query.c_str()) != SQLITE_OK)
+ {
+ std::string message = db->getErrorMsg();
+ if (err)
+ {
+ message.append(" (");
+ message.append(err);
+ message.append(")");
+ sqlite3_free(err);
+ }
+ throw DbErrors("%s", message.c_str());
+ }
+ } // end of for
+
+ if (db->in_transaction() && autocommit)
+ db->commit_transaction();
+
+ active = true;
+ ds_state = dsSelect;
+ if (autorefresh)
+ refresh();
+
+ } // end of try
+ catch (...)
+ {
+ if (db->in_transaction())
+ db->rollback_transaction();
+ throw;
+ }
+}
+
+void SqliteDataset::make_insert()
+{
+ make_query(insert_sql);
+ last();
+}
+
+void SqliteDataset::make_edit()
+{
+ make_query(update_sql);
+}
+
+void SqliteDataset::make_deletion()
+{
+ make_query(delete_sql);
+}
+
+void SqliteDataset::fill_fields()
+{
+ //cout <<"rr "<<result.records.size()<<"|" << frecno <<"\n";
+ if ((db == NULL) || (result.record_header.empty()) ||
+ (result.records.size() < (unsigned int)frecno))
+ return;
+
+ if (fields_object->size() == 0) // Filling columns name
+ {
+ const unsigned int ncols = result.record_header.size();
+ fields_object->resize(ncols);
+ for (unsigned int i = 0; i < ncols; i++)
+ {
+ (*fields_object)[i].props = result.record_header[i];
+ std::string name = result.record_header[i].name;
+ name2indexMap.insert({str_toLower(name.data()), i});
+ }
+ }
+
+ //Filling result
+ if (result.records.size() != 0)
+ {
+ const sql_record* row = result.records[frecno];
+ if (row)
+ {
+ const unsigned int ncols = row->size();
+ fields_object->resize(ncols);
+ for (unsigned int i = 0; i < ncols; i++)
+ (*fields_object)[i].val = row->at(i);
+ return;
+ }
+ }
+ const unsigned int ncols = result.record_header.size();
+ fields_object->resize(ncols);
+ for (unsigned int i = 0; i < ncols; i++)
+ (*fields_object)[i].val = "";
+}
+
+//------------- public functions implementation -----------------//
+bool SqliteDataset::dropIndex(const char* table, const char* index)
+{
+ std::string sql;
+
+ sql = static_cast<SqliteDatabase*>(db)->prepare("DROP INDEX IF EXISTS %s", index);
+
+ return (exec(sql) == SQLITE_OK);
+}
+
+int SqliteDataset::exec(const std::string& sql)
+{
+ if (!handle())
+ throw DbErrors("No Database Connection");
+ std::string qry = sql;
+ int res;
+ exec_res.clear();
+
+ // Strip size constraints from indexes (not supported in sqlite)
+ //
+ // Example:
+ // before: CREATE UNIQUE INDEX ixPath ON path ( strPath(255) )
+ // after: CREATE UNIQUE INDEX ixPath ON path ( strPath )
+ //
+ // NOTE: unexpected results occur if brackets are not matched
+ if (qry.find("CREATE UNIQUE INDEX") != std::string::npos ||
+ (qry.find("CREATE INDEX") != std::string::npos))
+ {
+ size_t pos = 0;
+ size_t pos2 = 0;
+
+ if ((pos = qry.find('(')) != std::string::npos)
+ {
+ pos++;
+ while ((pos = qry.find('(', pos)) != std::string::npos)
+ {
+ if ((pos2 = qry.find(')', pos)) != std::string::npos)
+ {
+ qry.replace(pos, pos2 - pos + 1, "");
+ pos = pos2;
+ }
+ }
+ }
+ }
+ // Strip ON table from DROP INDEX statements:
+ // before: DROP INDEX foo ON table
+ // after: DROP INDEX foo
+ size_t pos = qry.find("DROP INDEX ");
+ if (pos != std::string::npos)
+ {
+ pos = qry.find(" ON ", pos + 1);
+
+ if (pos != std::string::npos)
+ qry = qry.substr(0, pos);
+ }
+
+ char* errmsg;
+ if ((res = db->setErr(sqlite3_exec(handle(), qry.c_str(), &callback, &exec_res, &errmsg),
+ qry.c_str())) == SQLITE_OK)
+ return res;
+ else
+ {
+ if (errmsg)
+ {
+ DbErrors err("%s (%s)", db->getErrorMsg(), errmsg);
+ sqlite3_free(errmsg);
+ throw err;
+ }
+ else
+ {
+ throw DbErrors("%s", db->getErrorMsg());
+ }
+ }
+}
+
+int SqliteDataset::exec()
+{
+ return exec(sql);
+}
+
+const void* SqliteDataset::getExecRes()
+{
+ return &exec_res;
+}
+
+bool SqliteDataset::query(const std::string& query)
+{
+ if (!handle())
+ throw DbErrors("No Database Connection");
+ const std::string& qry = query;
+ int fs = qry.find("select");
+ int fS = qry.find("SELECT");
+ if (!(fs >= 0 || fS >= 0))
+ throw DbErrors("MUST be select SQL!");
+
+ close();
+
+ sqlite3_stmt* stmt = NULL;
+ if (db->setErr(sqlite3_prepare_v2(handle(), query.c_str(), -1, &stmt, NULL), query.c_str()) !=
+ SQLITE_OK)
+ throw DbErrors("%s", db->getErrorMsg());
+
+ // column headers
+ const unsigned int numColumns = sqlite3_column_count(stmt);
+ result.record_header.resize(numColumns);
+ for (unsigned int i = 0; i < numColumns; i++)
+ result.record_header[i].name = sqlite3_column_name(stmt, i);
+
+ // returned rows
+ while (sqlite3_step(stmt) == SQLITE_ROW)
+ { // have a row of data
+ sql_record* res = new sql_record;
+ res->resize(numColumns);
+ for (unsigned int i = 0; i < numColumns; i++)
+ {
+ field_value& v = res->at(i);
+ switch (sqlite3_column_type(stmt, i))
+ {
+ case SQLITE_INTEGER:
+ v.set_asInt64(sqlite3_column_int64(stmt, i));
+ break;
+ case SQLITE_FLOAT:
+ v.set_asDouble(sqlite3_column_double(stmt, i));
+ break;
+ case SQLITE_TEXT:
+ v.set_asString((const char*)sqlite3_column_text(stmt, i));
+ break;
+ case SQLITE_BLOB:
+ v.set_asString((const char*)sqlite3_column_text(stmt, i));
+ break;
+ case SQLITE_NULL:
+ default:
+ v.set_asString("");
+ v.set_isNull();
+ break;
+ }
+ }
+ result.records.push_back(res);
+ }
+ if (db->setErr(sqlite3_finalize(stmt), query.c_str()) == SQLITE_OK)
+ {
+ active = true;
+ ds_state = dsSelect;
+ this->first();
+ return true;
+ }
+ else
+ {
+ throw DbErrors("%s", db->getErrorMsg());
+ }
+}
+
+void SqliteDataset::open(const std::string& sql)
+{
+ set_select_sql(sql);
+ open();
+}
+
+void SqliteDataset::open()
+{
+ if (select_sql.size())
+ {
+ query(select_sql);
+ }
+ else
+ {
+ ds_state = dsInactive;
+ }
+}
+
+void SqliteDataset::close()
+{
+ Dataset::close();
+ result.clear();
+ edit_object->clear();
+ fields_object->clear();
+ ds_state = dsInactive;
+ active = false;
+}
+
+void SqliteDataset::cancel()
+{
+ if ((ds_state == dsInsert) || (ds_state == dsEdit))
+ {
+ if (result.record_header.size())
+ ds_state = dsSelect;
+ else
+ ds_state = dsInactive;
+ }
+}
+
+int SqliteDataset::num_rows()
+{
+ return result.records.size();
+}
+
+bool SqliteDataset::eof()
+{
+ return feof;
+}
+
+bool SqliteDataset::bof()
+{
+ return fbof;
+}
+
+void SqliteDataset::first()
+{
+ Dataset::first();
+ this->fill_fields();
+}
+
+void SqliteDataset::last()
+{
+ Dataset::last();
+ fill_fields();
+}
+
+void SqliteDataset::prev(void)
+{
+ Dataset::prev();
+ fill_fields();
+}
+
+void SqliteDataset::next(void)
+{
+ Dataset::next();
+ if (!eof())
+ fill_fields();
+}
+
+void SqliteDataset::free_row(void)
+{
+ if (frecno < 0 || (unsigned int)frecno >= result.records.size())
+ return;
+
+ sql_record* row = result.records[frecno];
+ if (row)
+ {
+ delete row;
+ result.records[frecno] = NULL;
+ }
+}
+
+bool SqliteDataset::seek(int pos)
+{
+ if (ds_state == dsSelect)
+ {
+ Dataset::seek(pos);
+ fill_fields();
+ return true;
+ }
+ return false;
+}
+
+int64_t SqliteDataset::lastinsertid()
+{
+ if (!handle())
+ throw DbErrors("No Database Connection");
+ return sqlite3_last_insert_rowid(handle());
+}
+
+long SqliteDataset::nextid(const char* seq_name)
+{
+ if (handle())
+ return db->nextid(seq_name);
+ else
+ return DB_UNEXPECTED_RESULT;
+}
+
+void SqliteDataset::interrupt()
+{
+ sqlite3_interrupt(handle());
+}
+} // namespace dbiplus
diff --git a/xbmc/dbwrappers/sqlitedataset.h b/xbmc/dbwrappers/sqlitedataset.h
new file mode 100644
index 0000000..4695dfd
--- /dev/null
+++ b/xbmc/dbwrappers/sqlitedataset.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2004, Leo Seib, Hannover
+ *
+ * Project:SQLiteDataset C++ Dynamic Library
+ * Module: SQLiteDataset class header file
+ * Author: Leo Seib E-Mail: leoseib@web.de
+ * Begin: 5/04/2002
+ *
+ * SPDX-License-Identifier: MIT
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "dataset.h"
+
+#include <stdio.h>
+
+#include <sqlite3.h>
+
+namespace dbiplus
+{
+/***************** Class SqliteDatabase definition ******************
+
+ class 'SqliteDatabase' connects with Sqlite-server
+
+******************************************************************/
+class SqliteDatabase : public Database
+{
+protected:
+ /* connect descriptor */
+ sqlite3* conn;
+ bool _in_transaction;
+ int last_err;
+
+public:
+ /* default constructor */
+ SqliteDatabase();
+ /* destructor */
+ ~SqliteDatabase() override;
+
+ Dataset* CreateDataset() const override;
+
+ /* func. returns connection handle with SQLite-server */
+ sqlite3* getHandle() { return conn; }
+ /* func. returns current status about SQLite-server connection */
+ int status() override;
+ int setErr(int err_code, const char* qry) override;
+ /* func. returns error message if error occurs */
+ const char* getErrorMsg() override;
+ /* sets a new host name */
+ void setHostName(const char* newHost) override;
+ /* sets a database name */
+ void setDatabase(const char* newDb) override;
+
+ /* func. connects to database-server */
+
+ int connect(bool create) override;
+ /* func. disconnects from database-server */
+ void disconnect() override;
+ /* func. creates new database */
+ int create() override;
+ /* func. deletes database */
+ int drop() override;
+ /* check if database exists (ie has tables/views defined) */
+ bool exists() override;
+
+ /* \brief copy database */
+ int copy(const char* backup_name) override;
+
+ /* \brief drop all extra analytics from database */
+ int drop_analytics(void) override;
+
+ long nextid(const char* seq_name) override;
+
+ /* virtual methods for transaction */
+
+ void start_transaction() override;
+ void commit_transaction() override;
+ void rollback_transaction() override;
+
+ /* virtual methods for formatting */
+ std::string vprepare(const char* format, va_list args) override;
+
+ bool in_transaction() override { return _in_transaction; }
+};
+
+/***************** Class SqliteDataset definition *******************
+
+ class 'SqliteDataset' does a query to SQLite-server
+
+******************************************************************/
+
+class SqliteDataset : public Dataset
+{
+protected:
+ sqlite3* handle();
+
+ /* Makes direct queries to database */
+ virtual void make_query(StringList& _sql);
+ /* Makes direct inserts into database */
+ void make_insert() override;
+ /* Edit SQL */
+ void make_edit() override;
+ /* Delete SQL */
+ void make_deletion() override;
+
+ //static int sqlite_callback(void* res_ptr,int ncol, char** result, char** cols);
+
+ /* This function works only with MySQL database
+ Filling the fields information from select statement */
+ void fill_fields() override;
+ /* Changing field values during dataset navigation */
+ virtual void free_row(); // free the memory allocated for the current row
+
+public:
+ /* constructor */
+ SqliteDataset();
+ explicit SqliteDataset(SqliteDatabase* newDb);
+
+ /* destructor */
+ ~SqliteDataset() override;
+
+ /* set autorefresh boolean value (if true - refresh the data after edit()
+or insert() operations default = false) */
+ void set_autorefresh(bool val);
+
+ /* opens a query & then sets a query results */
+ void open() override;
+ void open(const std::string& sql) override;
+ /* func. executes a query without results to return */
+ int exec() override;
+ int exec(const std::string& sql) override;
+ const void* getExecRes() override;
+ /* as open, but with our query exec Sql */
+ bool query(const std::string& query) override;
+ /* func. closes a query */
+ void close(void) override;
+ /* Cancel changes, made in insert or edit states of dataset */
+ void cancel() override;
+ /* last inserted id */
+ int64_t lastinsertid() override;
+ /* sequence numbers */
+ long nextid(const char* seq_name) override;
+ /* sequence numbers */
+ int num_rows() override;
+ /* interrupt any pending database operation */
+ void interrupt() override;
+
+ bool bof() override;
+ bool eof() override;
+ void first() override;
+ void last() override;
+ void prev() override;
+ void next() override;
+ /* Go to record No (starting with 0) */
+ bool seek(int pos = 0) override;
+
+ bool dropIndex(const char* table, const char* index) override;
+};
+} // namespace dbiplus