[Test] Added tests for CUtil::SplitParams
[xbmc.git] / xbmc / dbwrappers / mysqldataset.cpp
blobe277eefd8f4d7928e3628f5443c81e3c713e5714
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "mysqldataset.h"
11 #include "Util.h"
12 #include "network/DNSNameCache.h"
13 #include "network/WakeOnAccess.h"
14 #include "utils/StringUtils.h"
15 #include "utils/log.h"
17 #include <algorithm>
18 #include <array>
19 #include <iostream>
20 #include <set>
21 #include <string>
22 #ifdef HAS_MYSQL
23 #include <mysql/errmsg.h>
24 #elif defined(HAS_MARIADB)
25 #include <mariadb/errmsg.h>
26 #endif
28 #ifdef TARGET_POSIX
29 #include "platform/posix/ConvUtils.h"
30 #endif
32 #define MYSQL_OK 0
33 #define ER_BAD_DB_ERROR 1049
35 namespace dbiplus
38 //************* MysqlDatabase implementation ***************
40 MysqlDatabase::MysqlDatabase()
43 active = false;
44 _in_transaction = false; // for transaction
46 error = "Unknown database error"; //S_NO_CONNECTION;
47 host = "localhost";
48 port = "3306";
49 db = "mysql";
50 login = "root";
51 passwd = "null";
52 conn = NULL;
53 default_charset = "";
56 MysqlDatabase::~MysqlDatabase()
58 disconnect();
61 Dataset* MysqlDatabase::CreateDataset() const
63 return new MysqlDataset(const_cast<MysqlDatabase*>(this));
66 int MysqlDatabase::status(void)
68 if (active == false)
69 return DB_CONNECTION_NONE;
70 return DB_CONNECTION_OK;
73 int MysqlDatabase::setErr(int err_code, const char* qry)
75 switch (err_code)
77 case MYSQL_OK:
78 error = "Successful result";
79 break;
80 case CR_COMMANDS_OUT_OF_SYNC:
81 error = "Commands were executed in an improper order";
82 break;
83 case CR_SERVER_GONE_ERROR:
84 error = "The MySQL server has gone away";
85 break;
86 case CR_SERVER_LOST:
87 error = "The connection to the server was lost during this query";
88 break;
89 case CR_UNKNOWN_ERROR:
90 error = "An unknown error occurred";
91 break;
92 case 1146: /* ER_NO_SUCH_TABLE */
93 error = "The table does not exist";
94 break;
95 default:
96 char err[256];
97 snprintf(err, sizeof(err), "Undefined MySQL error: Code (%d)", err_code);
98 error = err;
100 error = "[" + db + "] " + error;
101 error += "\nQuery: ";
102 error += qry;
103 error += "\n";
104 return err_code;
107 const char* MysqlDatabase::getErrorMsg()
109 return error.c_str();
112 void MysqlDatabase::configure_connection()
114 char sqlcmd[512];
115 int ret;
117 // MySQL 5.7.5+: See #8393
118 strcpy(sqlcmd,
119 "SET SESSION sql_mode = (SELECT REPLACE(@@SESSION.sql_mode,'ONLY_FULL_GROUP_BY',''))");
120 if ((ret = mysql_real_query(conn, sqlcmd, strlen(sqlcmd))) != MYSQL_OK)
121 throw DbErrors("Can't disable sql_mode ONLY_FULL_GROUP_BY: '%s' (%d)", db.c_str(), ret);
123 // MySQL 5.7.6+: See #8393. Non-fatal if error, as not supported by MySQL 5.0.x
124 strcpy(sqlcmd, "SELECT @@SESSION.optimizer_switch");
125 if ((ret = mysql_real_query(conn, sqlcmd, strlen(sqlcmd))) == MYSQL_OK)
127 MYSQL_RES* res = mysql_store_result(conn);
128 MYSQL_ROW row;
130 if (res)
132 if ((row = mysql_fetch_row(res)) != NULL)
134 std::string column = row[0];
135 std::vector<std::string> split = StringUtils::Split(column, ',');
137 for (std::string& itIn : split)
139 if (StringUtils::Trim(itIn) == "derived_merge=on")
141 strcpy(sqlcmd, "SET SESSION optimizer_switch = 'derived_merge=off'");
142 if ((ret = mysql_real_query(conn, sqlcmd, strlen(sqlcmd))) != MYSQL_OK)
143 throw DbErrors("Can't set optimizer_switch = '%s': '%s' (%d)",
144 StringUtils::Trim(itIn).c_str(), db.c_str(), ret);
145 break;
149 mysql_free_result(res);
152 else
153 CLog::Log(LOGWARNING, "Unable to query optimizer_switch: '{}' ({})", db, ret);
156 int MysqlDatabase::connect(bool create_new)
158 if (host.empty() || db.empty())
159 return DB_CONNECTION_NONE;
161 std::string resolvedHost;
162 if (!StringUtils::EqualsNoCase(host, "localhost") && CDNSNameCache::Lookup(host, resolvedHost))
164 CLog::Log(LOGDEBUG, "{} replacing configured host {} with resolved host {}", __FUNCTION__, host,
165 resolvedHost);
166 host = resolvedHost;
171 disconnect();
173 if (conn == NULL)
175 conn = mysql_init(conn);
176 mysql_ssl_set(conn, key.empty() ? NULL : key.c_str(), cert.empty() ? NULL : cert.c_str(),
177 ca.empty() ? NULL : ca.c_str(), capath.empty() ? NULL : capath.c_str(),
178 ciphers.empty() ? NULL : ciphers.c_str());
181 if (!CWakeOnAccess::GetInstance().WakeUpHost(host, "MySQL : " + db))
182 return DB_CONNECTION_NONE;
184 // establish connection with just user credentials
185 if (mysql_real_connect(conn, host.c_str(), login.c_str(), passwd.c_str(), NULL,
186 atoi(port.c_str()), NULL, compression ? CLIENT_COMPRESS : 0) != NULL)
188 static bool showed_ver_info = false;
189 if (!showed_ver_info)
191 std::string version_string = mysql_get_server_info(conn);
192 CLog::Log(LOGINFO, "MYSQL: Connected to version {}", version_string);
193 showed_ver_info = true;
194 unsigned long version = mysql_get_server_version(conn);
195 // Minimum for MySQL: 5.6 (5.5 is EOL)
196 unsigned long min_version = 50600;
197 if (version_string.find("MariaDB") != std::string::npos)
199 // Minimum for MariaDB: 5.5 (still supported)
200 min_version = 50500;
203 if (version < min_version)
205 CLog::Log(
206 LOGWARNING,
207 "MYSQL: Your database server version {} is very old and might not be supported in "
208 "future Kodi versions. Please consider upgrading to MySQL 5.7 or MariaDB 10.2.",
209 version_string);
213 // disable mysql autocommit since we handle it
214 //mysql_autocommit(conn, false);
216 // enforce utf8 charset usage
217 default_charset = mysql_character_set_name(conn);
218 if (mysql_set_character_set(conn, "utf8")) // returns 0 on success
220 CLog::Log(LOGERROR, "Unable to set utf8 charset: {} [{}]({})", db, mysql_errno(conn),
221 mysql_error(conn));
224 configure_connection();
226 // check existence
227 if (exists())
229 // nothing to see here
231 else if (create_new)
233 char sqlcmd[512];
234 int ret;
236 snprintf(sqlcmd, sizeof(sqlcmd),
237 "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci", db.c_str());
238 if ((ret = query_with_reconnect(sqlcmd)) != MYSQL_OK)
240 throw DbErrors("Can't create new database: '%s' (%d)", db.c_str(), ret);
244 if (mysql_select_db(conn, db.c_str()) == 0)
246 active = true;
247 return DB_CONNECTION_OK;
251 // if we failed above, either credentials were incorrect or the database didn't exist
252 if (mysql_errno(conn) == ER_BAD_DB_ERROR && create_new)
255 if (create() == MYSQL_OK)
257 active = true;
259 return DB_CONNECTION_OK;
263 CLog::Log(LOGERROR, "Unable to open database: {} [{}]({})", db, mysql_errno(conn),
264 mysql_error(conn));
266 return DB_CONNECTION_NONE;
268 catch (...)
270 CLog::Log(LOGERROR, "Unable to open database: {} ({})", db, GetLastError());
272 return DB_CONNECTION_NONE;
275 void MysqlDatabase::disconnect(void)
277 if (conn != NULL)
279 mysql_close(conn);
280 conn = NULL;
283 active = false;
286 int MysqlDatabase::create()
288 return connect(true);
291 int MysqlDatabase::drop()
293 if (!active)
294 throw DbErrors("Can't drop database: no active connection...");
295 char sqlcmd[512];
296 int ret;
297 snprintf(sqlcmd, sizeof(sqlcmd), "DROP DATABASE `%s`", db.c_str());
298 if ((ret = query_with_reconnect(sqlcmd)) != MYSQL_OK)
300 throw DbErrors("Can't drop database: '%s' (%d)", db.c_str(), ret);
302 disconnect();
303 return DB_COMMAND_OK;
306 int MysqlDatabase::copy(const char* backup_name)
308 if (!active || conn == NULL)
309 throw DbErrors("Can't copy database: no active connection...");
311 char sql[4096];
312 int ret;
314 // ensure we're connected to the db we are about to copy
315 if ((ret = mysql_select_db(conn, db.c_str())) != MYSQL_OK)
316 throw DbErrors("Can't connect to source database: '%s'", db.c_str());
318 // grab a list of base tables only (no views)
319 snprintf(sql, sizeof(sql), "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'");
320 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
321 throw DbErrors("Can't determine base tables for copy.");
323 // get list of all tables from old DB
324 MYSQL_RES* res = mysql_store_result(conn);
326 if (res)
328 if (mysql_num_rows(res) == 0)
330 mysql_free_result(res);
331 throw DbErrors("The source database was unexpectedly empty.");
334 // create the new database
335 snprintf(sql, sizeof(sql), "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci",
336 backup_name);
337 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
339 mysql_free_result(res);
340 throw DbErrors("Can't create database for copy: '%s' (%d)", db.c_str(), ret);
343 MYSQL_ROW row;
345 // duplicate each table from old db to new db
346 while ((row = mysql_fetch_row(res)) != NULL)
348 // copy the table definition
349 snprintf(sql, sizeof(sql), "CREATE TABLE `%s`.%s LIKE %s", backup_name, row[0], row[0]);
351 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
353 mysql_free_result(res);
354 throw DbErrors("Can't copy schema for table '%s'\nError: %d", row[0], ret);
357 // copy the table data
358 snprintf(sql, sizeof(sql), "INSERT INTO `%s`.%s SELECT * FROM %s", backup_name, row[0],
359 row[0]);
361 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
363 mysql_free_result(res);
364 throw DbErrors("Can't copy data for table '%s'\nError: %d", row[0], ret);
367 mysql_free_result(res);
369 // we don't recreate views, indices, or triggers on copy
370 // as we'll be dropping and recreating them anyway
373 return 1;
376 int MysqlDatabase::drop_analytics(void)
378 if (!active || conn == NULL)
379 throw DbErrors("Can't clean database: no active connection...");
381 char sql[4096];
382 int ret;
384 // ensure we're connected to the db we are about to clean from stuff
385 if ((ret = mysql_select_db(conn, db.c_str())) != MYSQL_OK)
386 throw DbErrors("Can't connect to database: '%s'", db.c_str());
388 // getting a list of indexes in the database
389 snprintf(sql, sizeof(sql),
390 "SELECT DISTINCT table_name, index_name "
391 "FROM information_schema.statistics "
392 "WHERE index_name != 'PRIMARY' AND table_schema = '%s'",
393 db.c_str());
394 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
395 throw DbErrors("Can't determine list of indexes to drop.");
397 // we will acquire lists here
398 MYSQL_RES* res = mysql_store_result(conn);
399 MYSQL_ROW row;
401 if (res)
403 while ((row = mysql_fetch_row(res)) != NULL)
405 snprintf(sql, sizeof(sql), "ALTER TABLE `%s`.%s DROP INDEX %s", db.c_str(), row[0], row[1]);
407 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
409 mysql_free_result(res);
410 throw DbErrors("Can't drop index '%s'\nError: %d", row[0], ret);
413 mysql_free_result(res);
416 // next topic is a views list
417 snprintf(sql, sizeof(sql),
418 "SELECT table_name FROM information_schema.views WHERE table_schema = '%s'", db.c_str());
419 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
420 throw DbErrors("Can't determine list of views to drop.");
422 res = mysql_store_result(conn);
424 if (res)
426 while ((row = mysql_fetch_row(res)) != NULL)
428 /* we do not need IF EXISTS because these views are exist */
429 snprintf(sql, sizeof(sql), "DROP VIEW `%s`.%s", db.c_str(), row[0]);
431 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
433 mysql_free_result(res);
434 throw DbErrors("Can't drop view '%s'\nError: %d", row[0], ret);
437 mysql_free_result(res);
440 // triggers
441 snprintf(sql, sizeof(sql),
442 "SELECT trigger_name FROM information_schema.triggers WHERE event_object_schema = '%s'",
443 db.c_str());
444 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
445 throw DbErrors("Can't determine list of triggers to drop.");
447 res = mysql_store_result(conn);
449 if (res)
451 while ((row = mysql_fetch_row(res)) != NULL)
453 snprintf(sql, sizeof(sql), "DROP TRIGGER `%s`.%s", db.c_str(), row[0]);
455 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
457 mysql_free_result(res);
458 throw DbErrors("Can't drop trigger '%s'\nError: %d", row[0], ret);
461 mysql_free_result(res);
464 // Native functions
465 snprintf(sql, sizeof(sql),
466 "SELECT routine_name FROM information_schema.routines "
467 "WHERE routine_type = 'FUNCTION' and routine_schema = '%s'",
468 db.c_str());
469 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
470 throw DbErrors("Can't determine list of routines to drop.");
472 res = mysql_store_result(conn);
474 if (res)
476 while ((row = mysql_fetch_row(res)) != NULL)
478 snprintf(sql, sizeof(sql), "DROP FUNCTION `%s`.%s", db.c_str(), row[0]);
480 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
482 mysql_free_result(res);
483 throw DbErrors("Can't drop function '%s'\nError: %d", row[0], ret);
486 mysql_free_result(res);
489 return 1;
492 int MysqlDatabase::query_with_reconnect(const char* query)
494 int attempts = 5;
495 int result;
497 // try to reconnect if server is gone
498 while (((result = mysql_real_query(conn, query, strlen(query))) != MYSQL_OK) &&
499 ((result = mysql_errno(conn)) == CR_SERVER_GONE_ERROR || result == CR_SERVER_LOST) &&
500 (attempts-- > 0))
502 CLog::Log(LOGINFO, "MYSQL server has gone. Will try {} more attempt(s) to reconnect.",
503 attempts);
504 active = false;
505 connect(true);
508 return result;
511 long MysqlDatabase::nextid(const char* sname)
513 CLog::Log(LOGDEBUG, "MysqlDatabase::nextid for {}", sname);
514 if (!active)
515 return DB_UNEXPECTED_RESULT;
516 const char* seq_table = "sys_seq";
517 int id; /*,nrow,ncol;*/
518 MYSQL_RES* res;
519 char sqlcmd[512];
520 snprintf(sqlcmd, sizeof(sqlcmd), "SELECT nextid FROM %s WHERE seq_name = '%s'", seq_table, sname);
521 CLog::Log(LOGDEBUG, "MysqlDatabase::nextid will request");
522 if ((last_err = query_with_reconnect(sqlcmd)) != 0)
524 return DB_UNEXPECTED_RESULT;
526 res = mysql_store_result(conn);
527 if (res)
529 if (mysql_num_rows(res) == 0)
531 id = 1;
532 snprintf(sqlcmd, sizeof(sqlcmd), "INSERT INTO %s (nextid,seq_name) VALUES (%d,'%s')",
533 seq_table, id, sname);
534 mysql_free_result(res);
535 if ((last_err = query_with_reconnect(sqlcmd)) != 0)
536 return DB_UNEXPECTED_RESULT;
537 return id;
539 else
541 id = -1;
542 snprintf(sqlcmd, sizeof(sqlcmd), "UPDATE %s SET nextid=%d WHERE seq_name = '%s'", seq_table,
543 id, sname);
544 mysql_free_result(res);
545 if ((last_err = query_with_reconnect(sqlcmd)) != 0)
546 return DB_UNEXPECTED_RESULT;
547 return id;
550 return DB_UNEXPECTED_RESULT;
553 // methods for transactions
554 // ---------------------------------------------
555 void MysqlDatabase::start_transaction()
557 if (active)
559 mysql_autocommit(conn, false);
560 CLog::Log(LOGDEBUG, "Mysql Start transaction");
561 _in_transaction = true;
565 void MysqlDatabase::commit_transaction()
567 if (active)
569 mysql_commit(conn);
570 mysql_autocommit(conn, true);
571 CLog::Log(LOGDEBUG, "Mysql commit transaction");
572 _in_transaction = false;
576 void MysqlDatabase::rollback_transaction()
578 if (active)
580 mysql_rollback(conn);
581 mysql_autocommit(conn, true);
582 CLog::Log(LOGDEBUG, "Mysql rollback transaction");
583 _in_transaction = false;
587 bool MysqlDatabase::exists(void)
589 bool ret = false;
591 if (conn == NULL || mysql_ping(conn))
593 CLog::Log(LOGERROR, "Not connected to database, test of existence is not possible.");
594 return ret;
597 MYSQL_RES* result = mysql_list_dbs(conn, db.c_str());
598 if (result == NULL)
600 CLog::Log(LOGERROR, "Database is not present, does the user has CREATE DATABASE permission");
601 return false;
604 ret = (mysql_num_rows(result) > 0);
605 mysql_free_result(result);
607 // Check if there is some tables ( to permit user with no create database rights
608 if (ret)
610 result = mysql_list_tables(conn, NULL);
611 if (result != NULL)
612 ret = (mysql_num_rows(result) > 0);
614 mysql_free_result(result);
617 return ret;
620 // methods for formatting
621 // ---------------------------------------------
622 std::string MysqlDatabase::vprepare(const char* format, va_list args)
624 std::string strFormat = format;
625 std::string strResult = "";
626 size_t pos;
628 // %q is the sqlite format string for %s.
629 // Any bad character, like "'", will be replaced with a proper one
630 pos = 0;
631 while ((pos = strFormat.find("%s", pos)) != std::string::npos)
632 strFormat.replace(pos++, 2, "%q");
634 strResult = mysql_vmprintf(strFormat.c_str(), args);
635 // RAND() is the mysql form of RANDOM()
636 pos = 0;
637 while ((pos = strResult.find("RANDOM()", pos)) != std::string::npos)
639 strResult.replace(pos++, 8, "RAND()");
640 pos += 6;
643 // Replace some dataypes in CAST statements:
644 // before: CAST(iFoo AS TEXT), CAST(foo AS INTEGER)
645 // after: CAST(iFoo AS CHAR), CAST(foo AS SIGNED INTEGER)
646 pos = strResult.find("CAST(");
647 while (pos != std::string::npos)
649 size_t pos2 = strResult.find(" AS TEXT)", pos + 1);
650 if (pos2 != std::string::npos)
651 strResult.replace(pos2, 9, " AS CHAR)");
652 else
654 pos2 = strResult.find(" AS INTEGER)", pos + 1);
655 if (pos2 != std::string::npos)
656 strResult.replace(pos2, 12, " AS SIGNED INTEGER)");
658 pos = strResult.find("CAST(", pos + 1);
661 // Remove COLLATE NOCASE the SQLite case insensitive collation.
662 // In MySQL all tables are defined with case insensitive collation utf8_general_ci
663 pos = 0;
664 while ((pos = strResult.find(" COLLATE NOCASE", pos)) != std::string::npos)
665 strResult.erase(pos++, 15);
667 // Remove COLLATE ALPHANUM the SQLite custom collation.
668 pos = 0;
669 while ((pos = strResult.find(" COLLATE ALPHANUM", pos)) != std::string::npos)
670 strResult.erase(pos++, 15);
672 return strResult;
675 /* vsprintf() functionality is based on sqlite3.c functions */
678 ** Conversion types fall into various categories as defined by the
679 ** following enumeration.
681 #define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */
682 #define etFLOAT 2 /* Floating point. %f */
683 #define etEXP 3 /* Exponential notation. %e and %E */
684 #define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */
685 #define etSIZE 5 /* Return number of characters processed so far. %n */
686 #define etSTRING 6 /* Strings. %s */
687 #define etDYNSTRING 7 /* Dynamically allocated strings. %z */
688 #define etPERCENT 8 /* Percent symbol. %% */
689 #define etCHARX 9 /* Characters. %c */
690 /* The rest are extensions, not normally found in printf() */
691 #define etSQLESCAPE 10 /* Strings with '\'' doubled. Strings with '\\' escaped. %q */
692 #define etSQLESCAPE2 \
693 11 /* Strings with '\'' doubled and enclosed in '',
694 NULL pointers replaced by SQL NULL. %Q */
695 #define etPOINTER 14 /* The %p conversion */
696 #define etSQLESCAPE3 15 /* %w -> Strings with '\"' doubled */
698 #define etINVALID 0 /* Any unrecognized conversion type */
701 ** An "etByte" is an 8-bit unsigned value.
703 typedef unsigned char etByte;
706 ** Each builtin conversion character (ex: the 'd' in "%d") is described
707 ** by an instance of the following structure
709 typedef struct et_info
710 { /* Information about each format field */
711 char fmttype; /* The format field code letter */
712 etByte base; /* The base for radix conversion */
713 etByte flags; /* One or more of FLAG_ constants below */
714 etByte type; /* Conversion paradigm */
715 etByte charset; /* Offset into aDigits[] of the digits string */
716 etByte prefix; /* Offset into aPrefix[] of the prefix string */
717 } et_info;
720 ** An objected used to accumulate the text of a string where we
721 ** do not necessarily know how big the string will be in the end.
723 struct StrAccum
725 char* zBase; /* A base allocation. Not from malloc. */
726 char* zText; /* The string collected so far */
727 int nChar; /* Length of the string so far */
728 int nAlloc; /* Amount of space allocated in zText */
729 int mxAlloc; /* Maximum allowed string length */
730 bool mallocFailed; /* Becomes true if any memory allocation fails */
731 bool tooBig; /* Becomes true if string size exceeds limits */
735 ** Allowed values for et_info.flags
737 #define FLAG_SIGNED 1 /* True if the value to convert is signed */
738 #define FLAG_INTERN 2 /* True if for internal use only */
739 #define FLAG_STRING 4 /* Allow infinity precision */
742 ** The following table is searched linearly, so it is good to put the
743 ** most frequently used conversion types first.
745 static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
746 static const char aPrefix[] = "-x0\000X0";
747 // clang-format off
748 constexpr std::array<et_info, 20> fmtinfo = {{
749 {'d', 10, 1, etRADIX, 0, 0},
750 {'s', 0, 4, etSTRING, 0, 0},
751 {'g', 0, 1, etGENERIC, 30, 0},
752 {'z', 0, 4, etDYNSTRING, 0, 0},
753 {'q', 0, 4, etSQLESCAPE, 0, 0},
754 {'Q', 0, 4, etSQLESCAPE2, 0, 0},
755 {'w', 0, 4, etSQLESCAPE3, 0, 0},
756 {'c', 0, 0, etCHARX, 0, 0},
757 {'o', 8, 0, etRADIX, 0, 2},
758 {'u', 10, 0, etRADIX, 0, 0},
759 {'x', 16, 0, etRADIX, 16, 1},
760 {'X', 16, 0, etRADIX, 0, 4},
761 {'f', 0, 1, etFLOAT, 0, 0},
762 {'e', 0, 1, etEXP, 30, 0},
763 {'E', 0, 1, etEXP, 14, 0},
764 {'G', 0, 1, etGENERIC, 14, 0},
765 {'i', 10, 1, etRADIX, 0, 0},
766 {'n', 0, 0, etSIZE, 0, 0},
767 {'%', 0, 0, etPERCENT, 0, 0},
768 {'p', 16, 0, etPOINTER, 0, 1},
770 // clang-format on
773 ** "*val" is a double such that 0.1 <= *val < 10.0
774 ** Return the ascii code for the leading digit of *val, then
775 ** multiply "*val" by 10.0 to renormalize.
777 ** Example:
778 ** input: *val = 3.14159
779 ** output: *val = 1.4159 function return = '3'
781 ** The counter *cnt is incremented each time. After counter exceeds
782 ** 16 (the number of significant digits in a 64-bit float) '0' is
783 ** always returned.
785 char MysqlDatabase::et_getdigit(double* val, int* cnt)
787 int digit;
788 double d;
789 if ((*cnt)++ >= 16)
790 return '0';
791 digit = (int)*val;
792 d = digit;
793 digit += '0';
794 *val = (*val - d) * 10.0;
795 return (char)digit;
799 ** Append N space characters to the given string buffer.
801 void MysqlDatabase::appendSpace(StrAccum* pAccum, int N)
803 static const char zSpaces[] = " ";
804 while (N >= (int)sizeof(zSpaces) - 1)
806 mysqlStrAccumAppend(pAccum, zSpaces, sizeof(zSpaces) - 1);
807 N -= sizeof(zSpaces) - 1;
809 if (N > 0)
811 mysqlStrAccumAppend(pAccum, zSpaces, N);
815 #ifndef MYSQL_PRINT_BUF_SIZE
816 #define MYSQL_PRINT_BUF_SIZE 350
817 #endif
819 #define etBUFSIZE MYSQL_PRINT_BUF_SIZE /* Size of the output buffer */
822 ** The maximum length of a TEXT or BLOB in bytes. This also
823 ** limits the size of a row in a table or index.
825 ** The hard limit is the ability of a 32-bit signed integer
826 ** to count the size: 2^31-1 or 2147483647.
828 #ifndef MYSQL_MAX_LENGTH
829 #define MYSQL_MAX_LENGTH 1000000000
830 #endif
833 ** The root program. All variations call this core.
835 ** INPUTS:
836 ** func This is a pointer to a function taking three arguments
837 ** 1. A pointer to anything. Same as the "arg" parameter.
838 ** 2. A pointer to the list of characters to be output
839 ** (Note, this list is NOT null terminated.)
840 ** 3. An integer number of characters to be output.
841 ** (Note: This number might be zero.)
843 ** arg This is the pointer to anything which will be passed as the
844 ** first argument to "func". Use it for whatever you like.
846 ** fmt This is the format string, as in the usual print.
848 ** ap This is a pointer to a list of arguments. Same as in
849 ** vfprint.
851 ** OUTPUTS:
852 ** The return value is the total number of characters sent to
853 ** the function "func". Returns -1 on a error.
855 ** Note that the order in which automatic variables are declared below
856 ** seems to make a big difference in determining how fast this beast
857 ** will run.
859 void MysqlDatabase::mysqlVXPrintf(StrAccum* pAccum, /* Accumulate results here */
860 int useExtended, /* Allow extended %-conversions */
861 const char* fmt, /* Format string */
862 va_list ap /* arguments */
865 int c; /* Next character in the format string */
866 char* bufpt; /* Pointer to the conversion buffer */
867 int precision; /* Precision of the current field */
868 int length; /* Length of the field */
869 int idx; /* A general purpose loop counter */
870 int width; /* Width of the current field */
871 etByte flag_leftjustify; /* True if "-" flag is present */
872 etByte flag_plussign; /* True if "+" flag is present */
873 etByte flag_blanksign; /* True if " " flag is present */
874 etByte flag_alternateform; /* True if "#" flag is present */
875 etByte flag_altform2; /* True if "!" flag is present */
876 etByte flag_zeropad; /* True if field width constant starts with zero */
877 etByte flag_long; /* True if "l" flag is present */
878 etByte flag_longlong; /* True if the "ll" flag is present */
879 etByte done; /* Loop termination flag */
880 uint64_t longvalue; /* Value for integer types */
881 double realvalue; /* Value for real types */
882 const et_info* infop; /* Pointer to the appropriate info structure */
883 char buf[etBUFSIZE]; /* Conversion buffer */
884 char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */
885 etByte xtype = 0; /* Conversion paradigm */
886 char* zExtra; /* Extra memory used for etTCLESCAPE conversions */
887 int exp, e2; /* exponent of real numbers */
888 double rounder; /* Used for rounding floating point values */
889 etByte flag_dp; /* True if decimal point should be shown */
890 etByte flag_rtz; /* True if trailing zeros should be removed */
891 etByte flag_exp; /* True to force display of the exponent */
892 int nsd; /* Number of significant digits returned */
894 length = 0;
895 bufpt = 0;
896 for (; (c = (*fmt)) != 0; ++fmt)
898 bool isLike = false;
899 if (c != '%')
901 int amt;
902 bufpt = const_cast<char*>(fmt);
903 amt = 1;
904 while ((c = (*++fmt)) != '%' && c != 0)
905 amt++;
906 isLike = mysqlStrAccumAppend(pAccum, bufpt, amt);
907 if (c == 0)
908 break;
910 if ((c = (*++fmt)) == 0)
912 mysqlStrAccumAppend(pAccum, "%", 1);
913 break;
915 /* Find out what flags are present */
916 flag_leftjustify = flag_plussign = flag_blanksign = flag_alternateform = flag_altform2 =
917 flag_zeropad = 0;
918 done = 0;
921 switch (c)
923 case '-':
924 flag_leftjustify = 1;
925 break;
926 case '+':
927 flag_plussign = 1;
928 break;
929 case ' ':
930 flag_blanksign = 1;
931 break;
932 case '#':
933 flag_alternateform = 1;
934 break;
935 case '!':
936 flag_altform2 = 1;
937 break;
938 case '0':
939 flag_zeropad = 1;
940 break;
941 default:
942 done = 1;
943 break;
945 } while (!done && (c = (*++fmt)) != 0);
946 /* Get the field width */
947 width = 0;
948 if (c == '*')
950 width = va_arg(ap, int);
951 if (width < 0)
953 flag_leftjustify = 1;
954 width = -width;
956 c = *++fmt;
958 else
960 while (c >= '0' && c <= '9')
962 width = width * 10 + c - '0';
963 c = *++fmt;
966 if (width > etBUFSIZE - 10)
968 width = etBUFSIZE - 10;
970 /* Get the precision */
971 if (c == '.')
973 precision = 0;
974 c = *++fmt;
975 if (c == '*')
977 precision = va_arg(ap, int);
978 if (precision < 0)
979 precision = -precision;
980 c = *++fmt;
982 else
984 while (c >= '0' && c <= '9')
986 precision = precision * 10 + c - '0';
987 c = *++fmt;
991 else
993 precision = -1;
995 /* Get the conversion type modifier */
996 if (c == 'l')
998 flag_long = 1;
999 c = *++fmt;
1000 if (c == 'l')
1002 flag_longlong = 1;
1003 c = *++fmt;
1005 else
1007 flag_longlong = 0;
1010 else
1012 flag_long = flag_longlong = 0;
1014 /* Fetch the info entry for the field */
1015 infop = fmtinfo.data();
1016 xtype = etINVALID;
1018 for (const auto& info : fmtinfo)
1020 if (c != info.fmttype)
1021 continue;
1023 infop = &info;
1025 if (useExtended || (infop->flags & FLAG_INTERN) == 0)
1027 xtype = infop->type;
1029 else
1031 return;
1034 break;
1037 zExtra = 0;
1039 /* Limit the precision to prevent overflowing buf[] during conversion */
1040 if (precision > etBUFSIZE - 40 && (infop->flags & FLAG_STRING) == 0)
1042 precision = etBUFSIZE - 40;
1046 ** At this point, variables are initialized as follows:
1048 ** flag_alternateform TRUE if a '#' is present.
1049 ** flag_altform2 TRUE if a '!' is present.
1050 ** flag_plussign TRUE if a '+' is present.
1051 ** flag_leftjustify TRUE if a '-' is present or if the
1052 ** field width was negative.
1053 ** flag_zeropad TRUE if the width began with 0.
1054 ** flag_long TRUE if the letter 'l' (ell) prefixed
1055 ** the conversion character.
1056 ** flag_longlong TRUE if the letter 'll' (ell ell) prefixed
1057 ** the conversion character.
1058 ** flag_blanksign TRUE if a ' ' is present.
1059 ** width The specified field width. This is
1060 ** always non-negative. Zero is the default.
1061 ** precision The specified precision. The default
1062 ** is -1.
1063 ** xtype The class of the conversion.
1064 ** infop Pointer to the appropriate info struct.
1066 switch (xtype)
1068 case etPOINTER:
1069 flag_longlong = sizeof(char*) == sizeof(int64_t);
1070 flag_long = sizeof(char*) == sizeof(long int);
1071 /* Fall through into the next case */
1072 [[fallthrough]];
1073 case etRADIX:
1074 if (infop->flags & FLAG_SIGNED)
1076 int64_t v;
1077 if (flag_longlong)
1079 v = va_arg(ap, int64_t);
1081 else if (flag_long)
1083 v = va_arg(ap, long int);
1085 else
1087 v = va_arg(ap, int);
1089 if (v < 0)
1091 longvalue = -v;
1092 prefix = '-';
1094 else
1096 longvalue = v;
1097 if (flag_plussign)
1098 prefix = '+';
1099 else if (flag_blanksign)
1100 prefix = ' ';
1101 else
1102 prefix = 0;
1105 else
1107 if (flag_longlong)
1109 longvalue = va_arg(ap, uint64_t);
1111 else if (flag_long)
1113 longvalue = va_arg(ap, unsigned long int);
1115 else
1117 longvalue = va_arg(ap, unsigned int);
1119 prefix = 0;
1121 if (longvalue == 0)
1122 flag_alternateform = 0;
1123 if (flag_zeropad && precision < width - (prefix != 0))
1125 precision = width - (prefix != 0);
1127 bufpt = &buf[etBUFSIZE - 1];
1129 const char* cset;
1130 int base;
1131 cset = &aDigits[infop->charset];
1132 base = infop->base;
1134 { /* Convert to ascii */
1135 *(--bufpt) = cset[longvalue % base];
1136 longvalue = longvalue / base;
1137 } while (longvalue > 0);
1139 length = (int)(&buf[etBUFSIZE - 1] - bufpt);
1140 for (idx = precision - length; idx > 0; idx--)
1142 *(--bufpt) = '0'; /* Zero pad */
1144 if (prefix)
1145 *(--bufpt) = prefix; /* Add sign */
1146 if (flag_alternateform && infop->prefix)
1147 { /* Add "0" or "0x" */
1148 const char* pre;
1149 char x;
1150 pre = &aPrefix[infop->prefix];
1151 for (; (x = (*pre)) != 0; pre++)
1152 *(--bufpt) = x;
1154 length = (int)(&buf[etBUFSIZE - 1] - bufpt);
1155 bufpt[length] = 0;
1156 break;
1157 case etFLOAT:
1158 case etEXP:
1159 case etGENERIC:
1160 realvalue = va_arg(ap, double);
1161 if (precision < 0)
1162 precision = 6; /* Set default precision */
1163 if (precision > etBUFSIZE / 2 - 10)
1164 precision = etBUFSIZE / 2 - 10;
1165 if (realvalue < 0.0)
1167 realvalue = -realvalue;
1168 prefix = '-';
1170 else
1172 if (flag_plussign)
1173 prefix = '+';
1174 else if (flag_blanksign)
1175 prefix = ' ';
1176 else
1177 prefix = 0;
1179 if (xtype == etGENERIC && precision > 0)
1180 precision--;
1181 /* It makes more sense to use 0.5 */
1182 for (idx = precision, rounder = 0.5; idx > 0; idx--, rounder *= 0.1)
1185 if (xtype == etFLOAT)
1186 realvalue += rounder;
1187 /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
1188 exp = 0;
1189 #if 0
1190 if( mysqlIsNaN((double)realvalue) ){
1191 bufpt = "NaN";
1192 length = 3;
1193 break;
1195 #endif
1196 if (realvalue > 0.0)
1198 while (realvalue >= 1e32 && exp <= 350)
1200 realvalue *= 1e-32;
1201 exp += 32;
1203 while (realvalue >= 1e8 && exp <= 350)
1205 realvalue *= 1e-8;
1206 exp += 8;
1208 while (realvalue >= 10.0 && exp <= 350)
1210 realvalue *= 0.1;
1211 exp++;
1213 while (realvalue < 1e-8)
1215 realvalue *= 1e8;
1216 exp -= 8;
1218 while (realvalue < 1.0)
1220 realvalue *= 10.0;
1221 exp--;
1223 if (exp > 350)
1225 if (prefix == '-')
1227 bufpt = const_cast<char*>("-Inf");
1229 else if (prefix == '+')
1231 bufpt = const_cast<char*>("+Inf");
1233 else
1235 bufpt = const_cast<char*>("Inf");
1237 length = strlen(bufpt);
1238 break;
1241 bufpt = buf;
1243 ** If the field type is etGENERIC, then convert to either etEXP
1244 ** or etFLOAT, as appropriate.
1246 flag_exp = xtype == etEXP;
1247 if (xtype != etFLOAT)
1249 realvalue += rounder;
1250 if (realvalue >= 10.0)
1252 realvalue *= 0.1;
1253 exp++;
1256 if (xtype == etGENERIC)
1258 flag_rtz = !flag_alternateform;
1259 if (exp < -4 || exp > precision)
1261 xtype = etEXP;
1263 else
1265 precision = precision - exp;
1266 xtype = etFLOAT;
1269 else
1271 flag_rtz = 0;
1273 if (xtype == etEXP)
1275 e2 = 0;
1277 else
1279 e2 = exp;
1281 nsd = 0;
1282 flag_dp = (precision > 0 ? 1 : 0) | flag_alternateform | flag_altform2;
1283 /* The sign in front of the number */
1284 if (prefix)
1286 *(bufpt++) = prefix;
1288 /* Digits prior to the decimal point */
1289 if (e2 < 0)
1291 *(bufpt++) = '0';
1293 else
1295 for (; e2 >= 0; e2--)
1297 *(bufpt++) = et_getdigit(&realvalue, &nsd);
1300 /* The decimal point */
1301 if (flag_dp)
1303 *(bufpt++) = '.';
1305 /* "0" digits after the decimal point but before the first
1306 ** significant digit of the number */
1307 for (e2++; e2 < 0; precision--, e2++)
1309 //ASSERT( precision>0 );
1310 *(bufpt++) = '0';
1312 /* Significant digits after the decimal point */
1313 while ((precision--) > 0)
1315 *(bufpt++) = et_getdigit(&realvalue, &nsd);
1317 /* Remove trailing zeros and the "." if no digits follow the "." */
1318 if (flag_rtz && flag_dp)
1320 while (bufpt[-1] == '0')
1321 *(--bufpt) = 0;
1322 //ASSERT( bufpt>buf );
1323 if (bufpt[-1] == '.')
1325 if (flag_altform2)
1327 *(bufpt++) = '0';
1329 else
1331 *(--bufpt) = 0;
1335 /* Add the "eNNN" suffix */
1336 if (flag_exp || xtype == etEXP)
1338 *(bufpt++) = aDigits[infop->charset];
1339 if (exp < 0)
1341 *(bufpt++) = '-';
1342 exp = -exp;
1344 else
1346 *(bufpt++) = '+';
1348 if (exp >= 100)
1350 *(bufpt++) = (char)((exp / 100) + '0'); /* 100's digit */
1351 exp %= 100;
1353 *(bufpt++) = (char)(exp / 10 + '0'); /* 10's digit */
1354 *(bufpt++) = (char)(exp % 10 + '0'); /* 1's digit */
1356 *bufpt = 0;
1358 /* The converted number is in buf[] and zero terminated. Output it.
1359 ** Note that the number is in the usual order, not reversed as with
1360 ** integer conversions. */
1361 length = (int)(bufpt - buf);
1362 bufpt = buf;
1364 /* Special case: Add leading zeros if the flag_zeropad flag is
1365 ** set and we are not left justified */
1366 if (flag_zeropad && !flag_leftjustify && length < width)
1368 int i;
1369 int nPad = width - length;
1370 for (i = width; i >= nPad; i--)
1372 bufpt[i] = bufpt[i - nPad];
1374 i = prefix != 0;
1375 while (nPad--)
1376 bufpt[i++] = '0';
1377 length = width;
1379 break;
1380 case etSIZE:
1381 *(va_arg(ap, int*)) = pAccum->nChar;
1382 length = width = 0;
1383 break;
1384 case etPERCENT:
1385 buf[0] = '%';
1386 bufpt = buf;
1387 length = 1;
1388 break;
1389 case etCHARX:
1390 c = va_arg(ap, int);
1391 buf[0] = (char)c;
1392 if (precision >= 0)
1394 for (idx = 1; idx < precision; idx++)
1395 buf[idx] = (char)c;
1396 length = precision;
1398 else
1400 length = 1;
1402 bufpt = buf;
1403 break;
1404 case etSTRING:
1405 case etDYNSTRING:
1406 bufpt = va_arg(ap, char*);
1407 if (bufpt == 0)
1409 bufpt = const_cast<char*>("");
1411 else if (xtype == etDYNSTRING)
1413 zExtra = bufpt;
1415 if (precision >= 0)
1417 for (length = 0; length < precision && bufpt[length]; length++)
1421 else
1423 length = strlen(bufpt);
1425 break;
1426 case etSQLESCAPE:
1427 case etSQLESCAPE2:
1428 case etSQLESCAPE3:
1430 int i, j, k, n, isnull;
1431 int needQuote;
1432 char ch;
1433 char q = ((xtype == etSQLESCAPE3) ? '"' : '\''); /* Quote character */
1434 std::string arg = va_arg(ap, char*);
1435 if (isLike)
1436 StringUtils::Replace(arg, "\\", "\\\\");
1437 const char* escarg = arg.c_str();
1439 isnull = escarg == 0;
1440 if (isnull)
1441 escarg = (xtype == etSQLESCAPE2 ? "NULL" : "(NULL)");
1442 k = precision;
1443 for (i = 0; k != 0 && (ch = escarg[i]) != 0; i++, k--)
1445 needQuote = !isnull && xtype == etSQLESCAPE2;
1446 n = i * 2 + 1 + needQuote * 2;
1447 if (n > etBUFSIZE)
1449 bufpt = zExtra = (char*)malloc(n);
1450 if (bufpt == 0)
1452 pAccum->mallocFailed = true;
1453 return;
1456 else
1458 bufpt = buf;
1460 j = 0;
1461 if (needQuote)
1462 bufpt[j++] = q;
1463 k = i;
1464 j += mysql_real_escape_string(conn, bufpt, escarg, k);
1465 if (needQuote)
1466 bufpt[j++] = q;
1467 bufpt[j] = 0;
1468 length = j;
1469 /* The precision in %q and %Q means how many input characters to
1470 ** consume, not the length of the output...
1471 ** if( precision>=0 && precision<length ) length = precision; */
1472 break;
1474 default:
1476 return;
1478 } /* End switch over the format type */
1480 ** The text of the conversion is pointed to by "bufpt" and is
1481 ** "length" characters long. The field width is "width". Do
1482 ** the output.
1484 if (!flag_leftjustify)
1486 int nspace;
1487 nspace = width - length;
1488 if (nspace > 0)
1490 appendSpace(pAccum, nspace);
1493 if (length > 0)
1495 mysqlStrAccumAppend(pAccum, bufpt, length);
1497 if (flag_leftjustify)
1499 int nspace;
1500 nspace = width - length;
1501 if (nspace > 0)
1503 appendSpace(pAccum, nspace);
1506 if (zExtra)
1508 free(zExtra);
1510 } /* End for loop over the format string */
1511 } /* End of function */
1514 ** Append N bytes of text from z to the StrAccum object.
1516 bool MysqlDatabase::mysqlStrAccumAppend(StrAccum* p, const char* z, int N)
1518 if (p->tooBig | p->mallocFailed)
1520 return false;
1522 if (N < 0)
1524 N = strlen(z);
1526 if (N == 0 || z == 0)
1528 return false;
1530 if (p->nChar + N >= p->nAlloc)
1532 char* zNew;
1533 int szNew = p->nChar;
1534 szNew += N + 1;
1535 if (szNew > p->mxAlloc)
1537 mysqlStrAccumReset(p);
1538 p->tooBig = true;
1539 return false;
1541 else
1543 p->nAlloc = szNew;
1545 zNew = (char*)malloc(p->nAlloc);
1546 if (zNew)
1548 memcpy(zNew, p->zText, p->nChar);
1549 mysqlStrAccumReset(p);
1550 p->zText = zNew;
1552 else
1554 p->mallocFailed = true;
1555 mysqlStrAccumReset(p);
1556 return false;
1560 bool isLike = false;
1561 std::string testString(z, N);
1562 if (testString.find("LIKE") != std::string::npos || testString.find("like") != std::string::npos)
1564 CLog::Log(LOGDEBUG,
1565 "This query part contains a like, we will double backslash in the next field: {}",
1566 testString);
1567 isLike = true;
1570 memcpy(&p->zText[p->nChar], z, N);
1571 p->nChar += N;
1572 return isLike;
1576 ** Finish off a string by making sure it is zero-terminated.
1577 ** Return a pointer to the resulting string. Return a NULL
1578 ** pointer if any kind of error was encountered.
1580 char* MysqlDatabase::mysqlStrAccumFinish(StrAccum* p)
1582 if (p->zText)
1584 p->zText[p->nChar] = 0;
1585 if (p->zText == p->zBase)
1587 p->zText = (char*)malloc(p->nChar + 1);
1588 if (p->zText)
1590 memcpy(p->zText, p->zBase, p->nChar + 1);
1592 else
1594 p->mallocFailed = true;
1598 return p->zText;
1602 ** Reset an StrAccum string. Reclaim all malloced memory.
1604 void MysqlDatabase::mysqlStrAccumReset(StrAccum* p)
1606 if (p->zText != p->zBase)
1608 free(p->zText);
1610 p->zText = 0;
1614 ** Initialize a string accumulator
1616 void MysqlDatabase::mysqlStrAccumInit(StrAccum* p, char* zBase, int n, int mx)
1618 p->zText = p->zBase = zBase;
1619 p->nChar = 0;
1620 p->nAlloc = n;
1621 p->mxAlloc = mx;
1622 p->tooBig = false;
1623 p->mallocFailed = false;
1627 ** Print into memory obtained from mysql_malloc(). Omit the internal
1628 ** %-conversion extensions.
1630 std::string MysqlDatabase::mysql_vmprintf(const char* zFormat, va_list ap)
1632 char zBase[MYSQL_PRINT_BUF_SIZE];
1633 StrAccum acc;
1635 mysqlStrAccumInit(&acc, zBase, sizeof(zBase), MYSQL_MAX_LENGTH);
1636 mysqlVXPrintf(&acc, 0, zFormat, ap);
1637 return mysqlStrAccumFinish(&acc);
1640 //************* MysqlDataset implementation ***************
1642 MysqlDataset::MysqlDataset() : Dataset()
1644 haveError = false;
1645 db = NULL;
1646 autorefresh = false;
1649 MysqlDataset::MysqlDataset(MysqlDatabase* newDb) : Dataset(newDb)
1651 haveError = false;
1652 db = newDb;
1653 autorefresh = false;
1656 MysqlDataset::~MysqlDataset()
1660 void MysqlDataset::set_autorefresh(bool val)
1662 autorefresh = val;
1665 //--------- protected functions implementation -----------------//
1667 MYSQL* MysqlDataset::handle()
1669 if (db != NULL)
1671 return static_cast<MysqlDatabase*>(db)->getHandle();
1674 return NULL;
1677 void MysqlDataset::make_query(StringList& _sql)
1679 std::string query;
1680 if (db == NULL)
1681 throw DbErrors("No Database Connection");
1684 if (autocommit)
1685 db->start_transaction();
1687 for (const std::string& i : _sql)
1689 query = i;
1690 Dataset::parse_sql(query);
1691 if ((static_cast<MysqlDatabase*>(db)->query_with_reconnect(query.c_str())) != MYSQL_OK)
1693 throw DbErrors(db->getErrorMsg());
1695 } // end of for
1697 if (db->in_transaction() && autocommit)
1698 db->commit_transaction();
1700 active = true;
1701 ds_state = dsSelect;
1702 if (autorefresh)
1703 refresh();
1704 } // end of try
1705 catch (...)
1707 if (db->in_transaction())
1708 db->rollback_transaction();
1709 throw;
1713 void MysqlDataset::make_insert()
1715 make_query(insert_sql);
1716 last();
1719 void MysqlDataset::make_edit()
1721 make_query(update_sql);
1724 void MysqlDataset::make_deletion()
1726 make_query(delete_sql);
1729 void MysqlDataset::fill_fields()
1731 if ((db == NULL) || (result.record_header.empty()) ||
1732 (result.records.size() < (unsigned int)frecno))
1733 return;
1735 if (fields_object->size() == 0) // Filling columns name
1737 const unsigned int ncols = result.record_header.size();
1738 fields_object->resize(ncols);
1739 for (unsigned int i = 0; i < ncols; i++)
1741 (*fields_object)[i].props = result.record_header[i];
1742 std::string name = result.record_header[i].name;
1743 name2indexMap.insert({str_toLower(name.data()), i});
1747 //Filling result
1748 if (result.records.size() != 0)
1750 const sql_record* row = result.records[frecno];
1751 if (row)
1753 const unsigned int ncols = row->size();
1754 fields_object->resize(ncols);
1755 for (unsigned int i = 0; i < ncols; i++)
1756 (*fields_object)[i].val = row->at(i);
1757 return;
1760 const unsigned int ncols = result.record_header.size();
1761 fields_object->resize(ncols);
1762 for (unsigned int i = 0; i < ncols; i++)
1763 (*fields_object)[i].val = "";
1766 //------------- public functions implementation -----------------//
1767 bool MysqlDataset::dropIndex(const char* table, const char* index)
1769 std::string sql;
1770 std::string sql_prepared;
1772 sql = "SELECT * FROM information_schema.statistics WHERE TABLE_SCHEMA=DATABASE() AND "
1773 "table_name='%s' AND index_name='%s'";
1774 sql_prepared = static_cast<MysqlDatabase*>(db)->prepare(sql.c_str(), table, index);
1776 if (!query(sql_prepared))
1777 return false;
1779 if (num_rows())
1781 sql = "ALTER TABLE %s DROP INDEX %s";
1782 sql_prepared = static_cast<MysqlDatabase*>(db)->prepare(sql.c_str(), table, index);
1784 if (exec(sql_prepared) != MYSQL_OK)
1785 return false;
1788 return true;
1791 static bool ci_test(char l, char r)
1793 return tolower(l) == tolower(r);
1796 static size_t ci_find(const std::string& where, const std::string& what)
1798 std::string::const_iterator loc =
1799 std::search(where.begin(), where.end(), what.begin(), what.end(), ci_test);
1800 if (loc == where.end())
1801 return std::string::npos;
1802 else
1803 return loc - where.begin();
1806 int MysqlDataset::exec(const std::string& sql)
1808 if (!handle())
1809 throw DbErrors("No Database Connection");
1810 std::string qry = sql;
1811 int res = 0;
1812 exec_res.clear();
1814 // enforce the "auto_increment" keyword to be appended to "integer primary key"
1815 size_t loc;
1817 if ((loc = ci_find(qry, "integer primary key")) != std::string::npos)
1819 qry = qry.insert(loc + 19, " auto_increment ");
1822 // force the charset and collation to UTF-8
1823 if (ci_find(qry, "CREATE TABLE") != std::string::npos ||
1824 ci_find(qry, "CREATE TEMPORARY TABLE") != std::string::npos)
1826 // If CREATE TABLE ... SELECT Syntax is used we need to add the encoding after the table before the select
1827 // e.g. CREATE TABLE x CHARACTER SET utf8 COLLATE utf8_general_ci [AS] SELECT * FROM y
1828 if ((loc = qry.find(" AS SELECT ")) != std::string::npos ||
1829 (loc = qry.find(" SELECT ")) != std::string::npos)
1831 qry = qry.insert(loc, " CHARACTER SET utf8 COLLATE utf8_general_ci");
1833 else
1834 qry += " CHARACTER SET utf8 COLLATE utf8_general_ci";
1837 CLog::Log(LOGDEBUG, "Mysql execute: {}", qry);
1839 if (db->setErr(static_cast<MysqlDatabase*>(db)->query_with_reconnect(qry.c_str()), qry.c_str()) !=
1840 MYSQL_OK)
1842 throw DbErrors(db->getErrorMsg());
1844 else
1846 //! @todo collect results and store in exec_res
1847 return res;
1851 int MysqlDataset::exec()
1853 return exec(sql);
1856 const void* MysqlDataset::getExecRes()
1858 return &exec_res;
1861 bool MysqlDataset::query(const std::string& query)
1863 if (!handle())
1864 throw DbErrors("No Database Connection");
1865 std::string qry = query;
1866 int fs = qry.find("select");
1867 int fS = qry.find("SELECT");
1868 if (!(fs >= 0 || fS >= 0))
1869 throw DbErrors("MUST be select SQL!");
1871 close();
1873 size_t loc;
1875 // mysql doesn't understand CAST(foo as integer) => change to CAST(foo as signed integer)
1876 while ((loc = ci_find(qry, "as integer)")) != std::string::npos)
1877 qry = qry.insert(loc + 3, "signed ");
1879 MYSQL_RES* stmt = NULL;
1881 if (static_cast<MysqlDatabase*>(db)->setErr(
1882 static_cast<MysqlDatabase*>(db)->query_with_reconnect(qry.c_str()), qry.c_str()) !=
1883 MYSQL_OK)
1884 throw DbErrors(db->getErrorMsg());
1886 MYSQL* conn = handle();
1887 stmt = mysql_store_result(conn);
1888 if (stmt == NULL)
1889 throw DbErrors("Missing result set!");
1891 // column headers
1892 const unsigned int numColumns = mysql_num_fields(stmt);
1893 MYSQL_FIELD* fields = mysql_fetch_fields(stmt);
1894 MYSQL_ROW row;
1895 result.record_header.resize(numColumns);
1896 for (unsigned int i = 0; i < numColumns; i++)
1897 result.record_header[i].name = fields[i].name;
1899 // returned rows
1900 while ((row = mysql_fetch_row(stmt)))
1901 { // have a row of data
1902 sql_record* res = new sql_record;
1903 res->resize(numColumns);
1904 for (unsigned int i = 0; i < numColumns; i++)
1906 field_value& v = res->at(i);
1907 switch (fields[i].type)
1909 case MYSQL_TYPE_LONGLONG:
1910 if (row[i] != nullptr)
1912 v.set_asInt64(strtoll(row[i], nullptr, 10));
1914 else
1916 v.set_asInt64(0);
1918 break;
1919 case MYSQL_TYPE_DECIMAL:
1920 case MYSQL_TYPE_NEWDECIMAL:
1921 case MYSQL_TYPE_TINY:
1922 case MYSQL_TYPE_SHORT:
1923 case MYSQL_TYPE_INT24:
1924 case MYSQL_TYPE_LONG:
1925 if (row[i] != NULL)
1927 v.set_asInt(atoi(row[i]));
1929 else
1931 v.set_asInt(0);
1933 break;
1934 case MYSQL_TYPE_FLOAT:
1935 case MYSQL_TYPE_DOUBLE:
1936 if (row[i] != NULL)
1938 v.set_asDouble(atof(row[i]));
1940 else
1942 v.set_asDouble(0);
1944 break;
1945 case MYSQL_TYPE_STRING:
1946 case MYSQL_TYPE_VAR_STRING:
1947 case MYSQL_TYPE_VARCHAR:
1948 if (row[i] != NULL)
1949 v.set_asString((const char*)row[i]);
1950 break;
1951 case MYSQL_TYPE_TINY_BLOB:
1952 case MYSQL_TYPE_MEDIUM_BLOB:
1953 case MYSQL_TYPE_LONG_BLOB:
1954 case MYSQL_TYPE_BLOB:
1955 if (row[i] != NULL)
1956 v.set_asString((const char*)row[i]);
1957 break;
1958 case MYSQL_TYPE_NULL:
1959 default:
1960 CLog::Log(LOGDEBUG, "MYSQL: Unknown field type: {}", fields[i].type);
1961 v.set_asString("");
1962 v.set_isNull();
1963 break;
1966 result.records.push_back(res);
1968 mysql_free_result(stmt);
1969 active = true;
1970 ds_state = dsSelect;
1971 this->first();
1972 return true;
1975 void MysqlDataset::open(const std::string& sql)
1977 set_select_sql(sql);
1978 open();
1981 void MysqlDataset::open()
1983 if (select_sql.size())
1985 query(select_sql);
1987 else
1989 ds_state = dsInactive;
1993 void MysqlDataset::close()
1995 Dataset::close();
1996 result.clear();
1997 edit_object->clear();
1998 fields_object->clear();
1999 ds_state = dsInactive;
2000 active = false;
2003 void MysqlDataset::cancel()
2005 if ((ds_state == dsInsert) || (ds_state == dsEdit))
2007 if (result.record_header.size())
2008 ds_state = dsSelect;
2009 else
2010 ds_state = dsInactive;
2014 int MysqlDataset::num_rows()
2016 return result.records.size();
2019 bool MysqlDataset::eof()
2021 return feof;
2024 bool MysqlDataset::bof()
2026 return fbof;
2029 void MysqlDataset::first()
2031 Dataset::first();
2032 this->fill_fields();
2035 void MysqlDataset::last()
2037 Dataset::last();
2038 fill_fields();
2041 void MysqlDataset::prev(void)
2043 Dataset::prev();
2044 fill_fields();
2047 void MysqlDataset::next(void)
2049 Dataset::next();
2050 if (!eof())
2051 fill_fields();
2054 void MysqlDataset::free_row(void)
2056 if (frecno < 0 || (unsigned int)frecno >= result.records.size())
2057 return;
2059 sql_record* row = result.records[frecno];
2060 if (row)
2062 delete row;
2063 result.records[frecno] = NULL;
2067 bool MysqlDataset::seek(int pos)
2069 if (ds_state == dsSelect)
2071 Dataset::seek(pos);
2072 fill_fields();
2073 return true;
2076 return false;
2079 int64_t MysqlDataset::lastinsertid()
2081 if (!handle())
2082 throw DbErrors("No Database Connection");
2083 return mysql_insert_id(handle());
2086 long MysqlDataset::nextid(const char* seq_name)
2088 if (handle())
2089 return db->nextid(seq_name);
2091 return DB_UNEXPECTED_RESULT;
2094 void MysqlDataset::interrupt()
2096 // Impossible
2099 } // namespace dbiplus