Merge pull request #26350 from jjd-uk/estuary_media_align
[xbmc.git] / xbmc / dbwrappers / mysqldataset.cpp
blob8888859d91f36851a0b860ad9c7944bb6a70c915
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 CLog::LogF(LOGDEBUG, "Copying from {} to {} at {}", db, backup_name, host);
313 char sql[4096];
314 int ret;
316 // ensure we're connected to the db we are about to copy
317 if ((ret = mysql_select_db(conn, db.c_str())) != MYSQL_OK)
318 throw DbErrors("Can't connect to source database: '%s'", db.c_str());
320 // grab a list of base tables only (no views)
321 snprintf(sql, sizeof(sql), "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'");
322 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
323 throw DbErrors("Can't determine base tables for copy.");
325 // get list of all tables from old DB
326 MYSQL_RES* res = mysql_store_result(conn);
328 if (res)
330 if (mysql_num_rows(res) == 0)
332 mysql_free_result(res);
333 throw DbErrors("The source database was unexpectedly empty.");
336 // create the new database
337 snprintf(sql, sizeof(sql), "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci",
338 backup_name);
339 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
341 mysql_free_result(res);
342 throw DbErrors("Can't create database for copy: '%s' (%d)", db.c_str(), ret);
345 MYSQL_ROW row;
347 // duplicate each table from old db to new db
348 while ((row = mysql_fetch_row(res)) != NULL)
350 // copy the table definition
351 snprintf(sql, sizeof(sql), "CREATE TABLE `%s`.%s LIKE %s", backup_name, row[0], row[0]);
353 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
355 mysql_free_result(res);
356 throw DbErrors("Can't copy schema for table '%s'\nError: %d", row[0], ret);
359 // copy the table data
360 snprintf(sql, sizeof(sql), "INSERT INTO `%s`.%s SELECT * FROM %s", backup_name, row[0],
361 row[0]);
363 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
365 mysql_free_result(res);
366 throw DbErrors("Can't copy data for table '%s'\nError: %d", row[0], ret);
369 mysql_free_result(res);
371 // we don't recreate views, indices, or triggers on copy
372 // as we'll be dropping and recreating them anyway
375 return 1;
378 int MysqlDatabase::drop_analytics(void)
380 if (!active || conn == NULL)
381 throw DbErrors("Can't clean database: no active connection...");
383 char sql[4096];
384 int ret;
386 // ensure we're connected to the db we are about to clean from stuff
387 if ((ret = mysql_select_db(conn, db.c_str())) != MYSQL_OK)
388 throw DbErrors("Can't connect to database: '%s'", db.c_str());
390 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning indexes from database {} at {}", db, host);
392 // getting a list of indexes in the database
393 snprintf(sql, sizeof(sql),
394 "SELECT DISTINCT table_name, index_name "
395 "FROM information_schema.statistics "
396 "WHERE index_name != 'PRIMARY' AND table_schema = '%s'",
397 db.c_str());
398 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
399 throw DbErrors("Can't determine list of indexes to drop.");
401 // we will acquire lists here
402 MYSQL_RES* res = mysql_store_result(conn);
403 MYSQL_ROW row;
405 if (res)
407 while ((row = mysql_fetch_row(res)) != NULL)
409 snprintf(sql, sizeof(sql), "ALTER TABLE `%s`.%s DROP INDEX %s", db.c_str(), row[0], row[1]);
411 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
413 mysql_free_result(res);
414 throw DbErrors("Can't drop index '%s'\nError: %d", row[0], ret);
417 mysql_free_result(res);
420 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning views from database {} at {}", db, host);
422 // next topic is a views list
423 snprintf(sql, sizeof(sql),
424 "SELECT table_name FROM information_schema.views WHERE table_schema = '%s'", db.c_str());
425 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
426 throw DbErrors("Can't determine list of views to drop.");
428 res = mysql_store_result(conn);
430 if (res)
432 while ((row = mysql_fetch_row(res)) != NULL)
434 /* we do not need IF EXISTS because these views are exist */
435 snprintf(sql, sizeof(sql), "DROP VIEW `%s`.%s", db.c_str(), row[0]);
437 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
439 mysql_free_result(res);
440 throw DbErrors("Can't drop view '%s'\nError: %d", row[0], ret);
443 mysql_free_result(res);
446 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning triggers from database {} at {}", db, host);
448 // triggers
449 snprintf(sql, sizeof(sql),
450 "SELECT trigger_name FROM information_schema.triggers WHERE event_object_schema = '%s'",
451 db.c_str());
452 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
453 throw DbErrors("Can't determine list of triggers to drop.");
455 res = mysql_store_result(conn);
457 if (res)
459 while ((row = mysql_fetch_row(res)) != NULL)
461 snprintf(sql, sizeof(sql), "DROP TRIGGER `%s`.%s", db.c_str(), row[0]);
463 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
465 mysql_free_result(res);
466 throw DbErrors("Can't drop trigger '%s'\nError: %d", row[0], ret);
469 mysql_free_result(res);
472 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning functions from database {} at {}", db, host);
474 // Native functions
475 snprintf(sql, sizeof(sql),
476 "SELECT routine_name FROM information_schema.routines "
477 "WHERE routine_type = 'FUNCTION' and routine_schema = '%s'",
478 db.c_str());
479 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
480 throw DbErrors("Can't determine list of routines to drop.");
482 res = mysql_store_result(conn);
484 if (res)
486 while ((row = mysql_fetch_row(res)) != NULL)
488 snprintf(sql, sizeof(sql), "DROP FUNCTION `%s`.%s", db.c_str(), row[0]);
490 if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
492 mysql_free_result(res);
493 throw DbErrors("Can't drop function '%s'\nError: %d", row[0], ret);
496 mysql_free_result(res);
499 return 1;
502 int MysqlDatabase::query_with_reconnect(const char* query)
504 int attempts = 5;
505 int result;
507 // try to reconnect if server is gone
508 while (((result = mysql_real_query(conn, query, strlen(query))) != MYSQL_OK) &&
509 ((result = mysql_errno(conn)) == CR_SERVER_GONE_ERROR || result == CR_SERVER_LOST) &&
510 (attempts-- > 0))
512 CLog::Log(LOGINFO, "MYSQL server has gone. Will try {} more attempt(s) to reconnect.",
513 attempts);
514 active = false;
515 connect(true);
518 return result;
521 long MysqlDatabase::nextid(const char* sname)
523 CLog::LogFC(LOGDEBUG, LOGDATABASE, "nextid for {}", sname);
525 if (!active)
526 return DB_UNEXPECTED_RESULT;
527 const char* seq_table = "sys_seq";
528 int id; /*,nrow,ncol;*/
529 MYSQL_RES* res;
530 char sqlcmd[512];
531 snprintf(sqlcmd, sizeof(sqlcmd), "SELECT nextid FROM %s WHERE seq_name = '%s'", seq_table, sname);
532 CLog::LogFC(LOGDEBUG, LOGDATABASE, "MysqlDatabase::nextid will request");
533 if ((last_err = query_with_reconnect(sqlcmd)) != 0)
535 return DB_UNEXPECTED_RESULT;
537 res = mysql_store_result(conn);
538 if (res)
540 if (mysql_num_rows(res) == 0)
542 id = 1;
543 snprintf(sqlcmd, sizeof(sqlcmd), "INSERT INTO %s (nextid,seq_name) VALUES (%d,'%s')",
544 seq_table, id, sname);
545 mysql_free_result(res);
546 if ((last_err = query_with_reconnect(sqlcmd)) != 0)
547 return DB_UNEXPECTED_RESULT;
548 return id;
550 else
552 id = -1;
553 snprintf(sqlcmd, sizeof(sqlcmd), "UPDATE %s SET nextid=%d WHERE seq_name = '%s'", seq_table,
554 id, sname);
555 mysql_free_result(res);
556 if ((last_err = query_with_reconnect(sqlcmd)) != 0)
557 return DB_UNEXPECTED_RESULT;
558 return id;
561 return DB_UNEXPECTED_RESULT;
564 // methods for transactions
565 // ---------------------------------------------
566 void MysqlDatabase::start_transaction()
568 if (active)
570 assert(!_in_transaction);
571 mysql_autocommit(conn, false);
572 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql Start transaction");
574 if (_in_transaction)
575 CLog::LogF(LOGERROR, "error: nested transactions are not supported.");
576 else
577 _in_transaction = true;
581 void MysqlDatabase::commit_transaction()
583 if (active)
585 assert(_in_transaction);
586 mysql_commit(conn);
587 mysql_autocommit(conn, true);
588 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql commit transaction");
589 _in_transaction = false;
593 void MysqlDatabase::rollback_transaction()
595 if (active)
597 assert(_in_transaction);
598 mysql_rollback(conn);
599 mysql_autocommit(conn, true);
600 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql rollback transaction");
601 _in_transaction = false;
605 bool MysqlDatabase::exists(void)
607 bool ret = false;
609 if (conn == NULL || mysql_ping(conn))
611 CLog::Log(LOGERROR, "Not connected to database, test of existence is not possible.");
612 return ret;
615 MYSQL_RES* result = mysql_list_dbs(conn, db.c_str());
616 if (result == NULL)
618 CLog::Log(LOGERROR, "Database is not present, does the user has CREATE DATABASE permission");
619 return false;
622 ret = (mysql_num_rows(result) > 0);
623 mysql_free_result(result);
625 // Check if there is some tables ( to permit user with no create database rights
626 if (ret)
628 result = mysql_list_tables(conn, NULL);
629 if (result != NULL)
630 ret = (mysql_num_rows(result) > 0);
632 mysql_free_result(result);
635 return ret;
638 // methods for formatting
639 // ---------------------------------------------
640 std::string MysqlDatabase::vprepare(const char* format, va_list args)
642 std::string strFormat = format;
643 std::string strResult = "";
644 size_t pos;
646 // %q is the sqlite format string for %s.
647 // Any bad character, like "'", will be replaced with a proper one
648 pos = 0;
649 while ((pos = strFormat.find("%s", pos)) != std::string::npos)
650 strFormat.replace(pos++, 2, "%q");
652 strResult = mysql_vmprintf(strFormat.c_str(), args);
653 // RAND() is the mysql form of RANDOM()
654 pos = 0;
655 while ((pos = strResult.find("RANDOM()", pos)) != std::string::npos)
657 strResult.replace(pos++, 8, "RAND()");
658 pos += 6;
661 // Replace some dataypes in CAST statements:
662 // before: CAST(iFoo AS TEXT), CAST(foo AS INTEGER)
663 // after: CAST(iFoo AS CHAR), CAST(foo AS SIGNED INTEGER)
664 pos = strResult.find("CAST(");
665 while (pos != std::string::npos)
667 size_t pos2 = strResult.find(" AS TEXT)", pos + 1);
668 if (pos2 != std::string::npos)
669 strResult.replace(pos2, 9, " AS CHAR)");
670 else
672 pos2 = strResult.find(" AS INTEGER)", pos + 1);
673 if (pos2 != std::string::npos)
674 strResult.replace(pos2, 12, " AS SIGNED INTEGER)");
676 pos = strResult.find("CAST(", pos + 1);
679 // Remove COLLATE NOCASE the SQLite case insensitive collation.
680 // In MySQL all tables are defined with case insensitive collation utf8_general_ci
681 pos = 0;
682 while ((pos = strResult.find(" COLLATE NOCASE", pos)) != std::string::npos)
683 strResult.erase(pos++, 15);
685 // Remove COLLATE ALPHANUM the SQLite custom collation.
686 pos = 0;
687 while ((pos = strResult.find(" COLLATE ALPHANUM", pos)) != std::string::npos)
688 strResult.erase(pos++, 15);
690 return strResult;
693 /* vsprintf() functionality is based on sqlite3.c functions */
696 ** Conversion types fall into various categories as defined by the
697 ** following enumeration.
699 #define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */
700 #define etFLOAT 2 /* Floating point. %f */
701 #define etEXP 3 /* Exponential notation. %e and %E */
702 #define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */
703 #define etSIZE 5 /* Return number of characters processed so far. %n */
704 #define etSTRING 6 /* Strings. %s */
705 #define etDYNSTRING 7 /* Dynamically allocated strings. %z */
706 #define etPERCENT 8 /* Percent symbol. %% */
707 #define etCHARX 9 /* Characters. %c */
708 /* The rest are extensions, not normally found in printf() */
709 #define etSQLESCAPE 10 /* Strings with '\'' doubled. Strings with '\\' escaped. %q */
710 #define etSQLESCAPE2 \
711 11 /* Strings with '\'' doubled and enclosed in '',
712 NULL pointers replaced by SQL NULL. %Q */
713 #define etPOINTER 14 /* The %p conversion */
714 #define etSQLESCAPE3 15 /* %w -> Strings with '\"' doubled */
716 #define etINVALID 0 /* Any unrecognized conversion type */
719 ** An "etByte" is an 8-bit unsigned value.
721 typedef unsigned char etByte;
724 ** Each builtin conversion character (ex: the 'd' in "%d") is described
725 ** by an instance of the following structure
727 typedef struct et_info
728 { /* Information about each format field */
729 char fmttype; /* The format field code letter */
730 etByte base; /* The base for radix conversion */
731 etByte flags; /* One or more of FLAG_ constants below */
732 etByte type; /* Conversion paradigm */
733 etByte charset; /* Offset into aDigits[] of the digits string */
734 etByte prefix; /* Offset into aPrefix[] of the prefix string */
735 } et_info;
738 ** An objected used to accumulate the text of a string where we
739 ** do not necessarily know how big the string will be in the end.
741 struct StrAccum
743 char* zBase; /* A base allocation. Not from malloc. */
744 char* zText; /* The string collected so far */
745 int nChar; /* Length of the string so far */
746 int nAlloc; /* Amount of space allocated in zText */
747 int mxAlloc; /* Maximum allowed string length */
748 bool mallocFailed; /* Becomes true if any memory allocation fails */
749 bool tooBig; /* Becomes true if string size exceeds limits */
753 ** Allowed values for et_info.flags
755 #define FLAG_SIGNED 1 /* True if the value to convert is signed */
756 #define FLAG_INTERN 2 /* True if for internal use only */
757 #define FLAG_STRING 4 /* Allow infinity precision */
760 ** The following table is searched linearly, so it is good to put the
761 ** most frequently used conversion types first.
763 static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
764 static const char aPrefix[] = "-x0\000X0";
765 // clang-format off
766 constexpr std::array<et_info, 20> fmtinfo = {{
767 {'d', 10, 1, etRADIX, 0, 0},
768 {'s', 0, 4, etSTRING, 0, 0},
769 {'g', 0, 1, etGENERIC, 30, 0},
770 {'z', 0, 4, etDYNSTRING, 0, 0},
771 {'q', 0, 4, etSQLESCAPE, 0, 0},
772 {'Q', 0, 4, etSQLESCAPE2, 0, 0},
773 {'w', 0, 4, etSQLESCAPE3, 0, 0},
774 {'c', 0, 0, etCHARX, 0, 0},
775 {'o', 8, 0, etRADIX, 0, 2},
776 {'u', 10, 0, etRADIX, 0, 0},
777 {'x', 16, 0, etRADIX, 16, 1},
778 {'X', 16, 0, etRADIX, 0, 4},
779 {'f', 0, 1, etFLOAT, 0, 0},
780 {'e', 0, 1, etEXP, 30, 0},
781 {'E', 0, 1, etEXP, 14, 0},
782 {'G', 0, 1, etGENERIC, 14, 0},
783 {'i', 10, 1, etRADIX, 0, 0},
784 {'n', 0, 0, etSIZE, 0, 0},
785 {'%', 0, 0, etPERCENT, 0, 0},
786 {'p', 16, 0, etPOINTER, 0, 1},
788 // clang-format on
791 ** "*val" is a double such that 0.1 <= *val < 10.0
792 ** Return the ascii code for the leading digit of *val, then
793 ** multiply "*val" by 10.0 to renormalize.
795 ** Example:
796 ** input: *val = 3.14159
797 ** output: *val = 1.4159 function return = '3'
799 ** The counter *cnt is incremented each time. After counter exceeds
800 ** 16 (the number of significant digits in a 64-bit float) '0' is
801 ** always returned.
803 char MysqlDatabase::et_getdigit(double* val, int* cnt)
805 int digit;
806 double d;
807 if ((*cnt)++ >= 16)
808 return '0';
809 digit = (int)*val;
810 d = digit;
811 digit += '0';
812 *val = (*val - d) * 10.0;
813 return (char)digit;
817 ** Append N space characters to the given string buffer.
819 void MysqlDatabase::appendSpace(StrAccum* pAccum, int N)
821 static const char zSpaces[] = " ";
822 while (N >= (int)sizeof(zSpaces) - 1)
824 mysqlStrAccumAppend(pAccum, zSpaces, sizeof(zSpaces) - 1);
825 N -= sizeof(zSpaces) - 1;
827 if (N > 0)
829 mysqlStrAccumAppend(pAccum, zSpaces, N);
833 #ifndef MYSQL_PRINT_BUF_SIZE
834 #define MYSQL_PRINT_BUF_SIZE 350
835 #endif
837 #define etBUFSIZE MYSQL_PRINT_BUF_SIZE /* Size of the output buffer */
840 ** The maximum length of a TEXT or BLOB in bytes. This also
841 ** limits the size of a row in a table or index.
843 ** The hard limit is the ability of a 32-bit signed integer
844 ** to count the size: 2^31-1 or 2147483647.
846 #ifndef MYSQL_MAX_LENGTH
847 #define MYSQL_MAX_LENGTH 1000000000
848 #endif
851 ** The root program. All variations call this core.
853 ** INPUTS:
854 ** func This is a pointer to a function taking three arguments
855 ** 1. A pointer to anything. Same as the "arg" parameter.
856 ** 2. A pointer to the list of characters to be output
857 ** (Note, this list is NOT null terminated.)
858 ** 3. An integer number of characters to be output.
859 ** (Note: This number might be zero.)
861 ** arg This is the pointer to anything which will be passed as the
862 ** first argument to "func". Use it for whatever you like.
864 ** fmt This is the format string, as in the usual print.
866 ** ap This is a pointer to a list of arguments. Same as in
867 ** vfprint.
869 ** OUTPUTS:
870 ** The return value is the total number of characters sent to
871 ** the function "func". Returns -1 on a error.
873 ** Note that the order in which automatic variables are declared below
874 ** seems to make a big difference in determining how fast this beast
875 ** will run.
877 void MysqlDatabase::mysqlVXPrintf(StrAccum* pAccum, /* Accumulate results here */
878 int useExtended, /* Allow extended %-conversions */
879 const char* fmt, /* Format string */
880 va_list ap /* arguments */
883 int c; /* Next character in the format string */
884 char* bufpt; /* Pointer to the conversion buffer */
885 int precision; /* Precision of the current field */
886 int length; /* Length of the field */
887 int idx; /* A general purpose loop counter */
888 int width; /* Width of the current field */
889 etByte flag_leftjustify; /* True if "-" flag is present */
890 etByte flag_plussign; /* True if "+" flag is present */
891 etByte flag_blanksign; /* True if " " flag is present */
892 etByte flag_alternateform; /* True if "#" flag is present */
893 etByte flag_altform2; /* True if "!" flag is present */
894 etByte flag_zeropad; /* True if field width constant starts with zero */
895 etByte flag_long; /* True if "l" flag is present */
896 etByte flag_longlong; /* True if the "ll" flag is present */
897 etByte done; /* Loop termination flag */
898 uint64_t longvalue; /* Value for integer types */
899 double realvalue; /* Value for real types */
900 const et_info* infop; /* Pointer to the appropriate info structure */
901 char buf[etBUFSIZE]; /* Conversion buffer */
902 char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */
903 etByte xtype = 0; /* Conversion paradigm */
904 char* zExtra; /* Extra memory used for etTCLESCAPE conversions */
905 int exp, e2; /* exponent of real numbers */
906 double rounder; /* Used for rounding floating point values */
907 etByte flag_dp; /* True if decimal point should be shown */
908 etByte flag_rtz; /* True if trailing zeros should be removed */
909 etByte flag_exp; /* True to force display of the exponent */
910 int nsd; /* Number of significant digits returned */
912 length = 0;
913 bufpt = 0;
914 for (; (c = (*fmt)) != 0; ++fmt)
916 bool isLike = false;
917 if (c != '%')
919 int amt;
920 bufpt = const_cast<char*>(fmt);
921 amt = 1;
922 while ((c = (*++fmt)) != '%' && c != 0)
923 amt++;
924 isLike = mysqlStrAccumAppend(pAccum, bufpt, amt);
925 if (c == 0)
926 break;
928 if ((c = (*++fmt)) == 0)
930 mysqlStrAccumAppend(pAccum, "%", 1);
931 break;
933 /* Find out what flags are present */
934 flag_leftjustify = flag_plussign = flag_blanksign = flag_alternateform = flag_altform2 =
935 flag_zeropad = 0;
936 done = 0;
939 switch (c)
941 case '-':
942 flag_leftjustify = 1;
943 break;
944 case '+':
945 flag_plussign = 1;
946 break;
947 case ' ':
948 flag_blanksign = 1;
949 break;
950 case '#':
951 flag_alternateform = 1;
952 break;
953 case '!':
954 flag_altform2 = 1;
955 break;
956 case '0':
957 flag_zeropad = 1;
958 break;
959 default:
960 done = 1;
961 break;
963 } while (!done && (c = (*++fmt)) != 0);
964 /* Get the field width */
965 width = 0;
966 if (c == '*')
968 width = va_arg(ap, int);
969 if (width < 0)
971 flag_leftjustify = 1;
972 width = -width;
974 c = *++fmt;
976 else
978 while (c >= '0' && c <= '9')
980 width = width * 10 + c - '0';
981 c = *++fmt;
984 if (width > etBUFSIZE - 10)
986 width = etBUFSIZE - 10;
988 /* Get the precision */
989 if (c == '.')
991 precision = 0;
992 c = *++fmt;
993 if (c == '*')
995 precision = va_arg(ap, int);
996 if (precision < 0)
997 precision = -precision;
998 c = *++fmt;
1000 else
1002 while (c >= '0' && c <= '9')
1004 precision = precision * 10 + c - '0';
1005 c = *++fmt;
1009 else
1011 precision = -1;
1013 /* Get the conversion type modifier */
1014 if (c == 'l')
1016 flag_long = 1;
1017 c = *++fmt;
1018 if (c == 'l')
1020 flag_longlong = 1;
1021 c = *++fmt;
1023 else
1025 flag_longlong = 0;
1028 else
1030 flag_long = flag_longlong = 0;
1032 /* Fetch the info entry for the field */
1033 infop = fmtinfo.data();
1034 xtype = etINVALID;
1036 for (const auto& info : fmtinfo)
1038 if (c != info.fmttype)
1039 continue;
1041 infop = &info;
1043 if (useExtended || (infop->flags & FLAG_INTERN) == 0)
1045 xtype = infop->type;
1047 else
1049 return;
1052 break;
1055 zExtra = 0;
1057 /* Limit the precision to prevent overflowing buf[] during conversion */
1058 if (precision > etBUFSIZE - 40 && (infop->flags & FLAG_STRING) == 0)
1060 precision = etBUFSIZE - 40;
1064 ** At this point, variables are initialized as follows:
1066 ** flag_alternateform TRUE if a '#' is present.
1067 ** flag_altform2 TRUE if a '!' is present.
1068 ** flag_plussign TRUE if a '+' is present.
1069 ** flag_leftjustify TRUE if a '-' is present or if the
1070 ** field width was negative.
1071 ** flag_zeropad TRUE if the width began with 0.
1072 ** flag_long TRUE if the letter 'l' (ell) prefixed
1073 ** the conversion character.
1074 ** flag_longlong TRUE if the letter 'll' (ell ell) prefixed
1075 ** the conversion character.
1076 ** flag_blanksign TRUE if a ' ' is present.
1077 ** width The specified field width. This is
1078 ** always non-negative. Zero is the default.
1079 ** precision The specified precision. The default
1080 ** is -1.
1081 ** xtype The class of the conversion.
1082 ** infop Pointer to the appropriate info struct.
1084 switch (xtype)
1086 case etPOINTER:
1087 flag_longlong = sizeof(char*) == sizeof(int64_t);
1088 flag_long = sizeof(char*) == sizeof(long int);
1089 /* Fall through into the next case */
1090 [[fallthrough]];
1091 case etRADIX:
1092 if (infop->flags & FLAG_SIGNED)
1094 int64_t v;
1095 if (flag_longlong)
1097 v = va_arg(ap, int64_t);
1099 else if (flag_long)
1101 v = va_arg(ap, long int);
1103 else
1105 v = va_arg(ap, int);
1107 if (v < 0)
1109 longvalue = -v;
1110 prefix = '-';
1112 else
1114 longvalue = v;
1115 if (flag_plussign)
1116 prefix = '+';
1117 else if (flag_blanksign)
1118 prefix = ' ';
1119 else
1120 prefix = 0;
1123 else
1125 if (flag_longlong)
1127 longvalue = va_arg(ap, uint64_t);
1129 else if (flag_long)
1131 longvalue = va_arg(ap, unsigned long int);
1133 else
1135 longvalue = va_arg(ap, unsigned int);
1137 prefix = 0;
1139 if (longvalue == 0)
1140 flag_alternateform = 0;
1141 if (flag_zeropad && precision < width - (prefix != 0))
1143 precision = width - (prefix != 0);
1145 bufpt = &buf[etBUFSIZE - 1];
1147 const char* cset;
1148 int base;
1149 cset = &aDigits[infop->charset];
1150 base = infop->base;
1152 { /* Convert to ascii */
1153 *(--bufpt) = cset[longvalue % base];
1154 longvalue = longvalue / base;
1155 } while (longvalue > 0);
1157 length = (int)(&buf[etBUFSIZE - 1] - bufpt);
1158 for (idx = precision - length; idx > 0; idx--)
1160 *(--bufpt) = '0'; /* Zero pad */
1162 if (prefix)
1163 *(--bufpt) = prefix; /* Add sign */
1164 if (flag_alternateform && infop->prefix)
1165 { /* Add "0" or "0x" */
1166 const char* pre;
1167 char x;
1168 pre = &aPrefix[infop->prefix];
1169 for (; (x = (*pre)) != 0; pre++)
1170 *(--bufpt) = x;
1172 length = (int)(&buf[etBUFSIZE - 1] - bufpt);
1173 bufpt[length] = 0;
1174 break;
1175 case etFLOAT:
1176 case etEXP:
1177 case etGENERIC:
1178 realvalue = va_arg(ap, double);
1179 if (precision < 0)
1180 precision = 6; /* Set default precision */
1181 if (precision > etBUFSIZE / 2 - 10)
1182 precision = etBUFSIZE / 2 - 10;
1183 if (realvalue < 0.0)
1185 realvalue = -realvalue;
1186 prefix = '-';
1188 else
1190 if (flag_plussign)
1191 prefix = '+';
1192 else if (flag_blanksign)
1193 prefix = ' ';
1194 else
1195 prefix = 0;
1197 if (xtype == etGENERIC && precision > 0)
1198 precision--;
1199 /* It makes more sense to use 0.5 */
1200 for (idx = precision, rounder = 0.5; idx > 0; idx--, rounder *= 0.1)
1203 if (xtype == etFLOAT)
1204 realvalue += rounder;
1205 /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
1206 exp = 0;
1207 #if 0
1208 if( mysqlIsNaN((double)realvalue) ){
1209 bufpt = "NaN";
1210 length = 3;
1211 break;
1213 #endif
1214 if (realvalue > 0.0)
1216 while (realvalue >= 1e32 && exp <= 350)
1218 realvalue *= 1e-32;
1219 exp += 32;
1221 while (realvalue >= 1e8 && exp <= 350)
1223 realvalue *= 1e-8;
1224 exp += 8;
1226 while (realvalue >= 10.0 && exp <= 350)
1228 realvalue *= 0.1;
1229 exp++;
1231 while (realvalue < 1e-8)
1233 realvalue *= 1e8;
1234 exp -= 8;
1236 while (realvalue < 1.0)
1238 realvalue *= 10.0;
1239 exp--;
1241 if (exp > 350)
1243 if (prefix == '-')
1245 bufpt = const_cast<char*>("-Inf");
1247 else if (prefix == '+')
1249 bufpt = const_cast<char*>("+Inf");
1251 else
1253 bufpt = const_cast<char*>("Inf");
1255 length = strlen(bufpt);
1256 break;
1259 bufpt = buf;
1261 ** If the field type is etGENERIC, then convert to either etEXP
1262 ** or etFLOAT, as appropriate.
1264 flag_exp = xtype == etEXP;
1265 if (xtype != etFLOAT)
1267 realvalue += rounder;
1268 if (realvalue >= 10.0)
1270 realvalue *= 0.1;
1271 exp++;
1274 if (xtype == etGENERIC)
1276 flag_rtz = !flag_alternateform;
1277 if (exp < -4 || exp > precision)
1279 xtype = etEXP;
1281 else
1283 precision = precision - exp;
1284 xtype = etFLOAT;
1287 else
1289 flag_rtz = 0;
1291 if (xtype == etEXP)
1293 e2 = 0;
1295 else
1297 e2 = exp;
1299 nsd = 0;
1300 flag_dp = (precision > 0 ? 1 : 0) | flag_alternateform | flag_altform2;
1301 /* The sign in front of the number */
1302 if (prefix)
1304 *(bufpt++) = prefix;
1306 /* Digits prior to the decimal point */
1307 if (e2 < 0)
1309 *(bufpt++) = '0';
1311 else
1313 for (; e2 >= 0; e2--)
1315 *(bufpt++) = et_getdigit(&realvalue, &nsd);
1318 /* The decimal point */
1319 if (flag_dp)
1321 *(bufpt++) = '.';
1323 /* "0" digits after the decimal point but before the first
1324 ** significant digit of the number */
1325 for (e2++; e2 < 0; precision--, e2++)
1327 //ASSERT( precision>0 );
1328 *(bufpt++) = '0';
1330 /* Significant digits after the decimal point */
1331 while ((precision--) > 0)
1333 *(bufpt++) = et_getdigit(&realvalue, &nsd);
1335 /* Remove trailing zeros and the "." if no digits follow the "." */
1336 if (flag_rtz && flag_dp)
1338 while (bufpt[-1] == '0')
1339 *(--bufpt) = 0;
1340 //ASSERT( bufpt>buf );
1341 if (bufpt[-1] == '.')
1343 if (flag_altform2)
1345 *(bufpt++) = '0';
1347 else
1349 *(--bufpt) = 0;
1353 /* Add the "eNNN" suffix */
1354 if (flag_exp || xtype == etEXP)
1356 *(bufpt++) = aDigits[infop->charset];
1357 if (exp < 0)
1359 *(bufpt++) = '-';
1360 exp = -exp;
1362 else
1364 *(bufpt++) = '+';
1366 if (exp >= 100)
1368 *(bufpt++) = (char)((exp / 100) + '0'); /* 100's digit */
1369 exp %= 100;
1371 *(bufpt++) = (char)(exp / 10 + '0'); /* 10's digit */
1372 *(bufpt++) = (char)(exp % 10 + '0'); /* 1's digit */
1374 *bufpt = 0;
1376 /* The converted number is in buf[] and zero terminated. Output it.
1377 ** Note that the number is in the usual order, not reversed as with
1378 ** integer conversions. */
1379 length = (int)(bufpt - buf);
1380 bufpt = buf;
1382 /* Special case: Add leading zeros if the flag_zeropad flag is
1383 ** set and we are not left justified */
1384 if (flag_zeropad && !flag_leftjustify && length < width)
1386 int i;
1387 int nPad = width - length;
1388 for (i = width; i >= nPad; i--)
1390 bufpt[i] = bufpt[i - nPad];
1392 i = prefix != 0;
1393 while (nPad--)
1394 bufpt[i++] = '0';
1395 length = width;
1397 break;
1398 case etSIZE:
1399 *(va_arg(ap, int*)) = pAccum->nChar;
1400 length = width = 0;
1401 break;
1402 case etPERCENT:
1403 buf[0] = '%';
1404 bufpt = buf;
1405 length = 1;
1406 break;
1407 case etCHARX:
1408 c = va_arg(ap, int);
1409 buf[0] = (char)c;
1410 if (precision >= 0)
1412 for (idx = 1; idx < precision; idx++)
1413 buf[idx] = (char)c;
1414 length = precision;
1416 else
1418 length = 1;
1420 bufpt = buf;
1421 break;
1422 case etSTRING:
1423 case etDYNSTRING:
1424 bufpt = va_arg(ap, char*);
1425 if (bufpt == 0)
1427 bufpt = const_cast<char*>("");
1429 else if (xtype == etDYNSTRING)
1431 zExtra = bufpt;
1433 if (precision >= 0)
1435 for (length = 0; length < precision && bufpt[length]; length++)
1439 else
1441 length = strlen(bufpt);
1443 break;
1444 case etSQLESCAPE:
1445 case etSQLESCAPE2:
1446 case etSQLESCAPE3:
1448 int i, j, k, n, isnull;
1449 int needQuote;
1450 char ch;
1451 char q = ((xtype == etSQLESCAPE3) ? '"' : '\''); /* Quote character */
1452 std::string arg = va_arg(ap, char*);
1453 if (isLike)
1454 StringUtils::Replace(arg, "\\", "\\\\");
1455 const char* escarg = arg.c_str();
1457 isnull = escarg == 0;
1458 if (isnull)
1459 escarg = (xtype == etSQLESCAPE2 ? "NULL" : "(NULL)");
1460 k = precision;
1461 for (i = 0; k != 0 && (ch = escarg[i]) != 0; i++, k--)
1463 needQuote = !isnull && xtype == etSQLESCAPE2;
1464 n = i * 2 + 1 + needQuote * 2;
1465 if (n > etBUFSIZE)
1467 bufpt = zExtra = (char*)malloc(n);
1468 if (bufpt == 0)
1470 pAccum->mallocFailed = true;
1471 return;
1474 else
1476 bufpt = buf;
1478 j = 0;
1479 if (needQuote)
1480 bufpt[j++] = q;
1481 k = i;
1482 j += mysql_real_escape_string(conn, bufpt, escarg, k);
1483 if (needQuote)
1484 bufpt[j++] = q;
1485 bufpt[j] = 0;
1486 length = j;
1487 /* The precision in %q and %Q means how many input characters to
1488 ** consume, not the length of the output...
1489 ** if( precision>=0 && precision<length ) length = precision; */
1490 break;
1492 default:
1494 return;
1496 } /* End switch over the format type */
1498 ** The text of the conversion is pointed to by "bufpt" and is
1499 ** "length" characters long. The field width is "width". Do
1500 ** the output.
1502 if (!flag_leftjustify)
1504 int nspace;
1505 nspace = width - length;
1506 if (nspace > 0)
1508 appendSpace(pAccum, nspace);
1511 if (length > 0)
1513 mysqlStrAccumAppend(pAccum, bufpt, length);
1515 if (flag_leftjustify)
1517 int nspace;
1518 nspace = width - length;
1519 if (nspace > 0)
1521 appendSpace(pAccum, nspace);
1524 if (zExtra)
1526 free(zExtra);
1528 } /* End for loop over the format string */
1529 } /* End of function */
1532 ** Append N bytes of text from z to the StrAccum object.
1534 bool MysqlDatabase::mysqlStrAccumAppend(StrAccum* p, const char* z, int N)
1536 if (p->tooBig | p->mallocFailed)
1538 return false;
1540 if (N < 0)
1542 N = strlen(z);
1544 if (N == 0 || z == 0)
1546 return false;
1548 if (p->nChar + N >= p->nAlloc)
1550 char* zNew;
1551 int szNew = p->nChar;
1552 szNew += N + 1;
1553 if (szNew > p->mxAlloc)
1555 mysqlStrAccumReset(p);
1556 p->tooBig = true;
1557 return false;
1559 else
1561 p->nAlloc = szNew;
1563 zNew = (char*)malloc(p->nAlloc);
1564 if (zNew)
1566 memcpy(zNew, p->zText, p->nChar);
1567 mysqlStrAccumReset(p);
1568 p->zText = zNew;
1570 else
1572 p->mallocFailed = true;
1573 mysqlStrAccumReset(p);
1574 return false;
1578 bool isLike = false;
1579 std::string testString(z, N);
1580 if (testString.find("LIKE") != std::string::npos || testString.find("like") != std::string::npos)
1582 CLog::Log(LOGDEBUG,
1583 "This query part contains a like, we will double backslash in the next field: {}",
1584 testString);
1585 isLike = true;
1588 memcpy(&p->zText[p->nChar], z, N);
1589 p->nChar += N;
1590 return isLike;
1594 ** Finish off a string by making sure it is zero-terminated.
1595 ** Return a pointer to the resulting string. Return a NULL
1596 ** pointer if any kind of error was encountered.
1598 char* MysqlDatabase::mysqlStrAccumFinish(StrAccum* p)
1600 if (p->zText)
1602 p->zText[p->nChar] = 0;
1603 if (p->zText == p->zBase)
1605 p->zText = (char*)malloc(p->nChar + 1);
1606 if (p->zText)
1608 memcpy(p->zText, p->zBase, p->nChar + 1);
1610 else
1612 p->mallocFailed = true;
1616 return p->zText;
1620 ** Reset an StrAccum string. Reclaim all malloced memory.
1622 void MysqlDatabase::mysqlStrAccumReset(StrAccum* p)
1624 if (p->zText != p->zBase)
1626 free(p->zText);
1628 p->zText = 0;
1632 ** Initialize a string accumulator
1634 void MysqlDatabase::mysqlStrAccumInit(StrAccum* p, char* zBase, int n, int mx)
1636 p->zText = p->zBase = zBase;
1637 p->nChar = 0;
1638 p->nAlloc = n;
1639 p->mxAlloc = mx;
1640 p->tooBig = false;
1641 p->mallocFailed = false;
1645 ** Print into memory obtained from mysql_malloc(). Omit the internal
1646 ** %-conversion extensions.
1648 std::string MysqlDatabase::mysql_vmprintf(const char* zFormat, va_list ap)
1650 char zBase[MYSQL_PRINT_BUF_SIZE];
1651 StrAccum acc;
1653 mysqlStrAccumInit(&acc, zBase, sizeof(zBase), MYSQL_MAX_LENGTH);
1654 mysqlVXPrintf(&acc, 0, zFormat, ap);
1655 std::string strResult = mysqlStrAccumFinish(&acc);
1656 // Free acc.zText to avoid memory leak.
1657 mysqlStrAccumReset(&acc);
1658 return strResult;
1661 //************* MysqlDataset implementation ***************
1663 MysqlDataset::MysqlDataset() : Dataset()
1665 haveError = false;
1666 db = NULL;
1667 autorefresh = false;
1670 MysqlDataset::MysqlDataset(MysqlDatabase* newDb) : Dataset(newDb)
1672 haveError = false;
1673 db = newDb;
1674 autorefresh = false;
1677 MysqlDataset::~MysqlDataset()
1681 void MysqlDataset::set_autorefresh(bool val)
1683 autorefresh = val;
1686 //--------- protected functions implementation -----------------//
1688 MYSQL* MysqlDataset::handle()
1690 if (db != NULL)
1692 return static_cast<MysqlDatabase*>(db)->getHandle();
1695 return NULL;
1698 void MysqlDataset::make_query(StringList& _sql)
1700 std::string query;
1701 if (db == NULL)
1702 throw DbErrors("No Database Connection");
1705 if (autocommit)
1706 db->start_transaction();
1708 for (const std::string& i : _sql)
1710 query = i;
1711 Dataset::parse_sql(query);
1712 if ((static_cast<MysqlDatabase*>(db)->query_with_reconnect(query.c_str())) != MYSQL_OK)
1714 throw DbErrors(db->getErrorMsg());
1716 } // end of for
1718 if (db->in_transaction() && autocommit)
1719 db->commit_transaction();
1721 active = true;
1722 ds_state = dsSelect;
1723 if (autorefresh)
1724 refresh();
1725 } // end of try
1726 catch (...)
1728 if (db->in_transaction())
1729 db->rollback_transaction();
1730 throw;
1734 void MysqlDataset::make_insert()
1736 make_query(insert_sql);
1737 last();
1740 void MysqlDataset::make_edit()
1742 make_query(update_sql);
1745 void MysqlDataset::make_deletion()
1747 make_query(delete_sql);
1750 void MysqlDataset::fill_fields()
1752 if ((db == NULL) || (result.record_header.empty()) ||
1753 (result.records.size() < (unsigned int)frecno))
1754 return;
1756 if (fields_object->size() == 0) // Filling columns name
1758 const unsigned int ncols = result.record_header.size();
1759 fields_object->resize(ncols);
1760 for (unsigned int i = 0; i < ncols; i++)
1762 (*fields_object)[i].props = result.record_header[i];
1763 std::string name = result.record_header[i].name;
1764 name2indexMap.insert({str_toLower(name.data()), i});
1768 //Filling result
1769 if (result.records.size() != 0)
1771 const sql_record* row = result.records[frecno];
1772 if (row)
1774 const unsigned int ncols = row->size();
1775 fields_object->resize(ncols);
1776 for (unsigned int i = 0; i < ncols; i++)
1777 (*fields_object)[i].val = row->at(i);
1778 return;
1781 const unsigned int ncols = result.record_header.size();
1782 fields_object->resize(ncols);
1783 for (unsigned int i = 0; i < ncols; i++)
1784 (*fields_object)[i].val = "";
1787 //------------- public functions implementation -----------------//
1788 bool MysqlDataset::dropIndex(const char* table, const char* index)
1790 std::string sql;
1791 std::string sql_prepared;
1793 sql = "SELECT * FROM information_schema.statistics WHERE TABLE_SCHEMA=DATABASE() AND "
1794 "table_name='%s' AND index_name='%s'";
1795 sql_prepared = static_cast<MysqlDatabase*>(db)->prepare(sql.c_str(), table, index);
1797 if (!query(sql_prepared))
1798 return false;
1800 if (num_rows())
1802 sql = "ALTER TABLE %s DROP INDEX %s";
1803 sql_prepared = static_cast<MysqlDatabase*>(db)->prepare(sql.c_str(), table, index);
1805 if (exec(sql_prepared) != MYSQL_OK)
1806 return false;
1809 return true;
1812 static bool ci_test(char l, char r)
1814 return tolower(l) == tolower(r);
1817 static size_t ci_find(const std::string& where, const std::string& what)
1819 std::string::const_iterator loc =
1820 std::search(where.begin(), where.end(), what.begin(), what.end(), ci_test);
1821 if (loc == where.end())
1822 return std::string::npos;
1823 else
1824 return loc - where.begin();
1827 int MysqlDataset::exec(const std::string& sql)
1829 if (!handle())
1830 throw DbErrors("No Database Connection");
1831 std::string qry = sql;
1833 exec_res.clear();
1835 // enforce the "auto_increment" keyword to be appended to "integer primary key"
1836 size_t loc;
1838 if ((loc = ci_find(qry, "integer primary key")) != std::string::npos)
1840 qry = qry.insert(loc + 19, " auto_increment ");
1843 // force the charset and collation to UTF-8
1844 if (ci_find(qry, "CREATE TABLE") != std::string::npos ||
1845 ci_find(qry, "CREATE TEMPORARY TABLE") != std::string::npos)
1847 // If CREATE TABLE ... SELECT Syntax is used we need to add the encoding after the table before the select
1848 // e.g. CREATE TABLE x CHARACTER SET utf8 COLLATE utf8_general_ci [AS] SELECT * FROM y
1849 if ((loc = qry.find(" AS SELECT ")) != std::string::npos ||
1850 (loc = qry.find(" SELECT ")) != std::string::npos)
1852 qry = qry.insert(loc, " CHARACTER SET utf8 COLLATE utf8_general_ci");
1854 else
1855 qry += " CHARACTER SET utf8 COLLATE utf8_general_ci";
1858 const auto start = std::chrono::steady_clock::now();
1860 const int res =
1861 db->setErr(static_cast<MysqlDatabase*>(db)->query_with_reconnect(qry.c_str()), qry.c_str());
1863 const auto end = std::chrono::steady_clock::now();
1864 const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
1866 CLog::LogFC(LOGDEBUG, LOGDATABASE, "{} ms for query: {}", duration.count(), qry);
1868 if (res != MYSQL_OK)
1870 throw DbErrors(db->getErrorMsg());
1872 else
1874 //! @todo collect results and store in exec_res
1875 return res;
1879 int MysqlDataset::exec()
1881 return exec(sql);
1884 const void* MysqlDataset::getExecRes()
1886 return &exec_res;
1889 bool MysqlDataset::query(const std::string& query)
1891 if (!handle())
1892 throw DbErrors("No Database Connection");
1893 std::string qry = query;
1894 int fs = qry.find("select");
1895 int fS = qry.find("SELECT");
1896 if (!(fs >= 0 || fS >= 0))
1897 throw DbErrors("MUST be select SQL!");
1899 close();
1901 size_t loc;
1903 // mysql doesn't understand CAST(foo as integer) => change to CAST(foo as signed integer)
1904 while ((loc = ci_find(qry, "as integer)")) != std::string::npos)
1905 qry = qry.insert(loc + 3, "signed ");
1907 MYSQL_RES* stmt = NULL;
1909 if (static_cast<MysqlDatabase*>(db)->setErr(
1910 static_cast<MysqlDatabase*>(db)->query_with_reconnect(qry.c_str()), qry.c_str()) !=
1911 MYSQL_OK)
1912 throw DbErrors(db->getErrorMsg());
1914 MYSQL* conn = handle();
1915 stmt = mysql_store_result(conn);
1916 if (stmt == NULL)
1917 throw DbErrors("Missing result set!");
1919 // column headers
1920 const unsigned int numColumns = mysql_num_fields(stmt);
1921 MYSQL_FIELD* fields = mysql_fetch_fields(stmt);
1922 MYSQL_ROW row;
1923 result.record_header.resize(numColumns);
1924 for (unsigned int i = 0; i < numColumns; i++)
1925 result.record_header[i].name = fields[i].name;
1927 // returned rows
1928 while ((row = mysql_fetch_row(stmt)))
1929 { // have a row of data
1930 sql_record* res = new sql_record;
1931 res->resize(numColumns);
1932 for (unsigned int i = 0; i < numColumns; i++)
1934 field_value& v = res->at(i);
1935 switch (fields[i].type)
1937 case MYSQL_TYPE_LONGLONG:
1938 if (row[i] != nullptr)
1940 v.set_asInt64(strtoll(row[i], nullptr, 10));
1942 else
1944 v.set_asInt64(0);
1946 break;
1947 case MYSQL_TYPE_DECIMAL:
1948 case MYSQL_TYPE_NEWDECIMAL:
1949 case MYSQL_TYPE_TINY:
1950 case MYSQL_TYPE_SHORT:
1951 case MYSQL_TYPE_INT24:
1952 case MYSQL_TYPE_LONG:
1953 if (row[i] != NULL)
1955 v.set_asInt(atoi(row[i]));
1957 else
1959 v.set_asInt(0);
1961 break;
1962 case MYSQL_TYPE_FLOAT:
1963 case MYSQL_TYPE_DOUBLE:
1964 if (row[i] != NULL)
1966 v.set_asDouble(atof(row[i]));
1968 else
1970 v.set_asDouble(0);
1972 break;
1973 case MYSQL_TYPE_STRING:
1974 case MYSQL_TYPE_VAR_STRING:
1975 case MYSQL_TYPE_VARCHAR:
1976 if (row[i] != NULL)
1977 v.set_asString((const char*)row[i]);
1978 break;
1979 case MYSQL_TYPE_TINY_BLOB:
1980 case MYSQL_TYPE_MEDIUM_BLOB:
1981 case MYSQL_TYPE_LONG_BLOB:
1982 case MYSQL_TYPE_BLOB:
1983 if (row[i] != NULL)
1984 v.set_asString((const char*)row[i]);
1985 break;
1986 case MYSQL_TYPE_NULL:
1987 default:
1988 CLog::Log(LOGDEBUG, "MYSQL: Unknown field type: {}", fields[i].type);
1989 v.set_asString("");
1990 v.set_isNull();
1991 break;
1994 result.records.push_back(res);
1996 mysql_free_result(stmt);
1997 active = true;
1998 ds_state = dsSelect;
1999 this->first();
2000 return true;
2003 void MysqlDataset::open(const std::string& sql)
2005 set_select_sql(sql);
2006 open();
2009 void MysqlDataset::open()
2011 if (select_sql.size())
2013 query(select_sql);
2015 else
2017 ds_state = dsInactive;
2021 void MysqlDataset::close()
2023 Dataset::close();
2024 result.clear();
2025 edit_object->clear();
2026 fields_object->clear();
2027 ds_state = dsInactive;
2028 active = false;
2031 void MysqlDataset::cancel()
2033 if ((ds_state == dsInsert) || (ds_state == dsEdit))
2035 if (result.record_header.size())
2036 ds_state = dsSelect;
2037 else
2038 ds_state = dsInactive;
2042 int MysqlDataset::num_rows()
2044 return result.records.size();
2047 bool MysqlDataset::eof()
2049 return feof;
2052 bool MysqlDataset::bof()
2054 return fbof;
2057 void MysqlDataset::first()
2059 Dataset::first();
2060 this->fill_fields();
2063 void MysqlDataset::last()
2065 Dataset::last();
2066 fill_fields();
2069 void MysqlDataset::prev(void)
2071 Dataset::prev();
2072 fill_fields();
2075 void MysqlDataset::next(void)
2077 Dataset::next();
2078 if (!eof())
2079 fill_fields();
2082 void MysqlDataset::free_row(void)
2084 if (frecno < 0 || (unsigned int)frecno >= result.records.size())
2085 return;
2087 sql_record* row = result.records[frecno];
2088 if (row)
2090 delete row;
2091 result.records[frecno] = NULL;
2095 bool MysqlDataset::seek(int pos)
2097 if (ds_state == dsSelect)
2099 Dataset::seek(pos);
2100 fill_fields();
2101 return true;
2104 return false;
2107 int64_t MysqlDataset::lastinsertid()
2109 if (!handle())
2110 throw DbErrors("No Database Connection");
2111 return mysql_insert_id(handle());
2114 long MysqlDataset::nextid(const char* seq_name)
2116 if (handle())
2117 return db->nextid(seq_name);
2119 return DB_UNEXPECTED_RESULT;
2122 void MysqlDataset::interrupt()
2124 // Impossible
2127 } // namespace dbiplus