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.
9 #include "mysqldataset.h"
12 #include "network/DNSNameCache.h"
13 #include "network/WakeOnAccess.h"
14 #include "utils/StringUtils.h"
15 #include "utils/log.h"
23 #include <mysql/errmsg.h>
24 #elif defined(HAS_MARIADB)
25 #include <mariadb/errmsg.h>
29 #include "platform/posix/ConvUtils.h"
33 #define ER_BAD_DB_ERROR 1049
38 //************* MysqlDatabase implementation ***************
40 MysqlDatabase::MysqlDatabase()
44 _in_transaction
= false; // for transaction
46 error
= "Unknown database error"; //S_NO_CONNECTION;
56 MysqlDatabase::~MysqlDatabase()
61 Dataset
* MysqlDatabase::CreateDataset() const
63 return new MysqlDataset(const_cast<MysqlDatabase
*>(this));
66 int MysqlDatabase::status(void)
69 return DB_CONNECTION_NONE
;
70 return DB_CONNECTION_OK
;
73 int MysqlDatabase::setErr(int err_code
, const char* qry
)
78 error
= "Successful result";
80 case CR_COMMANDS_OUT_OF_SYNC
:
81 error
= "Commands were executed in an improper order";
83 case CR_SERVER_GONE_ERROR
:
84 error
= "The MySQL server has gone away";
87 error
= "The connection to the server was lost during this query";
89 case CR_UNKNOWN_ERROR
:
90 error
= "An unknown error occurred";
92 case 1146: /* ER_NO_SUCH_TABLE */
93 error
= "The table does not exist";
97 snprintf(err
, sizeof(err
), "Undefined MySQL error: Code (%d)", err_code
);
100 error
= "[" + db
+ "] " + error
;
101 error
+= "\nQuery: ";
107 const char* MysqlDatabase::getErrorMsg()
109 return error
.c_str();
112 void MysqlDatabase::configure_connection()
117 // MySQL 5.7.5+: See #8393
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
);
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
);
149 mysql_free_result(res
);
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
,
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)
203 if (version
< min_version
)
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.",
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
),
224 configure_connection();
229 // nothing to see here
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)
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
)
259 return DB_CONNECTION_OK
;
263 CLog::Log(LOGERROR
, "Unable to open database: {} [{}]({})", db
, mysql_errno(conn
),
266 return DB_CONNECTION_NONE
;
270 CLog::Log(LOGERROR
, "Unable to open database: {} ({})", db
, GetLastError());
272 return DB_CONNECTION_NONE
;
275 void MysqlDatabase::disconnect(void)
286 int MysqlDatabase::create()
288 return connect(true);
291 int MysqlDatabase::drop()
294 throw DbErrors("Can't drop database: no active connection...");
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
);
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
);
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
);
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",
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
);
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],
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
378 int MysqlDatabase::drop_analytics(void)
380 if (!active
|| conn
== NULL
)
381 throw DbErrors("Can't clean database: no active connection...");
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'",
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
);
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
);
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
);
449 snprintf(sql
, sizeof(sql
),
450 "SELECT trigger_name FROM information_schema.triggers WHERE event_object_schema = '%s'",
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
);
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
);
475 snprintf(sql
, sizeof(sql
),
476 "SELECT routine_name FROM information_schema.routines "
477 "WHERE routine_type = 'FUNCTION' and routine_schema = '%s'",
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
);
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
);
502 int MysqlDatabase::query_with_reconnect(const char* query
)
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
) &&
512 CLog::Log(LOGINFO
, "MYSQL server has gone. Will try {} more attempt(s) to reconnect.",
521 long MysqlDatabase::nextid(const char* sname
)
523 CLog::LogFC(LOGDEBUG
, LOGDATABASE
, "nextid for {}", sname
);
526 return DB_UNEXPECTED_RESULT
;
527 const char* seq_table
= "sys_seq";
528 int id
; /*,nrow,ncol;*/
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
);
540 if (mysql_num_rows(res
) == 0)
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
;
553 snprintf(sqlcmd
, sizeof(sqlcmd
), "UPDATE %s SET nextid=%d WHERE seq_name = '%s'", seq_table
,
555 mysql_free_result(res
);
556 if ((last_err
= query_with_reconnect(sqlcmd
)) != 0)
557 return DB_UNEXPECTED_RESULT
;
561 return DB_UNEXPECTED_RESULT
;
564 // methods for transactions
565 // ---------------------------------------------
566 void MysqlDatabase::start_transaction()
570 assert(!_in_transaction
);
571 mysql_autocommit(conn
, false);
572 CLog::LogFC(LOGDEBUG
, LOGDATABASE
, "Mysql Start transaction");
575 CLog::LogF(LOGERROR
, "error: nested transactions are not supported.");
577 _in_transaction
= true;
581 void MysqlDatabase::commit_transaction()
585 assert(_in_transaction
);
587 mysql_autocommit(conn
, true);
588 CLog::LogFC(LOGDEBUG
, LOGDATABASE
, "Mysql commit transaction");
589 _in_transaction
= false;
593 void MysqlDatabase::rollback_transaction()
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)
609 if (conn
== NULL
|| mysql_ping(conn
))
611 CLog::Log(LOGERROR
, "Not connected to database, test of existence is not possible.");
615 MYSQL_RES
* result
= mysql_list_dbs(conn
, db
.c_str());
618 CLog::Log(LOGERROR
, "Database is not present, does the user has CREATE DATABASE permission");
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
628 result
= mysql_list_tables(conn
, NULL
);
630 ret
= (mysql_num_rows(result
) > 0);
632 mysql_free_result(result
);
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
= "";
646 // %q is the sqlite format string for %s.
647 // Any bad character, like "'", will be replaced with a proper one
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()
655 while ((pos
= strResult
.find("RANDOM()", pos
)) != std::string::npos
)
657 strResult
.replace(pos
++, 8, "RAND()");
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)");
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
682 while ((pos
= strResult
.find(" COLLATE NOCASE", pos
)) != std::string::npos
)
683 strResult
.erase(pos
++, 15);
685 // Remove COLLATE ALPHANUM the SQLite custom collation.
687 while ((pos
= strResult
.find(" COLLATE ALPHANUM", pos
)) != std::string::npos
)
688 strResult
.erase(pos
++, 15);
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 */
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.
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";
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},
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.
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
803 char MysqlDatabase::et_getdigit(double* val
, int* cnt
)
812 *val
= (*val
- d
) * 10.0;
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;
829 mysqlStrAccumAppend(pAccum
, zSpaces
, N
);
833 #ifndef MYSQL_PRINT_BUF_SIZE
834 #define MYSQL_PRINT_BUF_SIZE 350
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
851 ** The root program. All variations call this core.
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
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
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 */
914 for (; (c
= (*fmt
)) != 0; ++fmt
)
920 bufpt
= const_cast<char*>(fmt
);
922 while ((c
= (*++fmt
)) != '%' && c
!= 0)
924 isLike
= mysqlStrAccumAppend(pAccum
, bufpt
, amt
);
928 if ((c
= (*++fmt
)) == 0)
930 mysqlStrAccumAppend(pAccum
, "%", 1);
933 /* Find out what flags are present */
934 flag_leftjustify
= flag_plussign
= flag_blanksign
= flag_alternateform
= flag_altform2
=
942 flag_leftjustify
= 1;
951 flag_alternateform
= 1;
963 } while (!done
&& (c
= (*++fmt
)) != 0);
964 /* Get the field width */
968 width
= va_arg(ap
, int);
971 flag_leftjustify
= 1;
978 while (c
>= '0' && c
<= '9')
980 width
= width
* 10 + c
- '0';
984 if (width
> etBUFSIZE
- 10)
986 width
= etBUFSIZE
- 10;
988 /* Get the precision */
995 precision
= va_arg(ap
, int);
997 precision
= -precision
;
1002 while (c
>= '0' && c
<= '9')
1004 precision
= precision
* 10 + c
- '0';
1013 /* Get the conversion type modifier */
1030 flag_long
= flag_longlong
= 0;
1032 /* Fetch the info entry for the field */
1033 infop
= fmtinfo
.data();
1036 for (const auto& info
: fmtinfo
)
1038 if (c
!= info
.fmttype
)
1043 if (useExtended
|| (infop
->flags
& FLAG_INTERN
) == 0)
1045 xtype
= infop
->type
;
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
1081 ** xtype The class of the conversion.
1082 ** infop Pointer to the appropriate info struct.
1087 flag_longlong
= sizeof(char*) == sizeof(int64_t);
1088 flag_long
= sizeof(char*) == sizeof(long int);
1089 /* Fall through into the next case */
1092 if (infop
->flags
& FLAG_SIGNED
)
1097 v
= va_arg(ap
, int64_t);
1101 v
= va_arg(ap
, long int);
1105 v
= va_arg(ap
, int);
1117 else if (flag_blanksign
)
1127 longvalue
= va_arg(ap
, uint64_t);
1131 longvalue
= va_arg(ap
, unsigned long int);
1135 longvalue
= va_arg(ap
, unsigned int);
1140 flag_alternateform
= 0;
1141 if (flag_zeropad
&& precision
< width
- (prefix
!= 0))
1143 precision
= width
- (prefix
!= 0);
1145 bufpt
= &buf
[etBUFSIZE
- 1];
1149 cset
= &aDigits
[infop
->charset
];
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 */
1163 *(--bufpt
) = prefix
; /* Add sign */
1164 if (flag_alternateform
&& infop
->prefix
)
1165 { /* Add "0" or "0x" */
1168 pre
= &aPrefix
[infop
->prefix
];
1169 for (; (x
= (*pre
)) != 0; pre
++)
1172 length
= (int)(&buf
[etBUFSIZE
- 1] - bufpt
);
1178 realvalue
= va_arg(ap
, double);
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
;
1192 else if (flag_blanksign
)
1197 if (xtype
== etGENERIC
&& precision
> 0)
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 */
1208 if( mysqlIsNaN((double)realvalue
) ){
1214 if (realvalue
> 0.0)
1216 while (realvalue
>= 1e32
&& exp
<= 350)
1221 while (realvalue
>= 1e8
&& exp
<= 350)
1226 while (realvalue
>= 10.0 && exp
<= 350)
1231 while (realvalue
< 1e-8)
1236 while (realvalue
< 1.0)
1245 bufpt
= const_cast<char*>("-Inf");
1247 else if (prefix
== '+')
1249 bufpt
= const_cast<char*>("+Inf");
1253 bufpt
= const_cast<char*>("Inf");
1255 length
= strlen(bufpt
);
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)
1274 if (xtype
== etGENERIC
)
1276 flag_rtz
= !flag_alternateform
;
1277 if (exp
< -4 || exp
> precision
)
1283 precision
= precision
- exp
;
1300 flag_dp
= (precision
> 0 ? 1 : 0) | flag_alternateform
| flag_altform2
;
1301 /* The sign in front of the number */
1304 *(bufpt
++) = prefix
;
1306 /* Digits prior to the decimal point */
1313 for (; e2
>= 0; e2
--)
1315 *(bufpt
++) = et_getdigit(&realvalue
, &nsd
);
1318 /* The decimal point */
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 );
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')
1340 //ASSERT( bufpt>buf );
1341 if (bufpt
[-1] == '.')
1353 /* Add the "eNNN" suffix */
1354 if (flag_exp
|| xtype
== etEXP
)
1356 *(bufpt
++) = aDigits
[infop
->charset
];
1368 *(bufpt
++) = (char)((exp
/ 100) + '0'); /* 100's digit */
1371 *(bufpt
++) = (char)(exp
/ 10 + '0'); /* 10's digit */
1372 *(bufpt
++) = (char)(exp
% 10 + '0'); /* 1's digit */
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
);
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
)
1387 int nPad
= width
- length
;
1388 for (i
= width
; i
>= nPad
; i
--)
1390 bufpt
[i
] = bufpt
[i
- nPad
];
1399 *(va_arg(ap
, int*)) = pAccum
->nChar
;
1408 c
= va_arg(ap
, int);
1412 for (idx
= 1; idx
< precision
; idx
++)
1424 bufpt
= va_arg(ap
, char*);
1427 bufpt
= const_cast<char*>("");
1429 else if (xtype
== etDYNSTRING
)
1435 for (length
= 0; length
< precision
&& bufpt
[length
]; length
++)
1441 length
= strlen(bufpt
);
1448 int i
, j
, k
, n
, isnull
;
1451 char q
= ((xtype
== etSQLESCAPE3
) ? '"' : '\''); /* Quote character */
1452 std::string arg
= va_arg(ap
, char*);
1454 StringUtils::Replace(arg
, "\\", "\\\\");
1455 const char* escarg
= arg
.c_str();
1457 isnull
= escarg
== 0;
1459 escarg
= (xtype
== etSQLESCAPE2
? "NULL" : "(NULL)");
1461 for (i
= 0; k
!= 0 && (ch
= escarg
[i
]) != 0; i
++, k
--)
1463 needQuote
= !isnull
&& xtype
== etSQLESCAPE2
;
1464 n
= i
* 2 + 1 + needQuote
* 2;
1467 bufpt
= zExtra
= (char*)malloc(n
);
1470 pAccum
->mallocFailed
= true;
1482 j
+= mysql_real_escape_string(conn
, bufpt
, escarg
, k
);
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; */
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
1502 if (!flag_leftjustify
)
1505 nspace
= width
- length
;
1508 appendSpace(pAccum
, nspace
);
1513 mysqlStrAccumAppend(pAccum
, bufpt
, length
);
1515 if (flag_leftjustify
)
1518 nspace
= width
- length
;
1521 appendSpace(pAccum
, nspace
);
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
)
1544 if (N
== 0 || z
== 0)
1548 if (p
->nChar
+ N
>= p
->nAlloc
)
1551 int szNew
= p
->nChar
;
1553 if (szNew
> p
->mxAlloc
)
1555 mysqlStrAccumReset(p
);
1563 zNew
= (char*)malloc(p
->nAlloc
);
1566 memcpy(zNew
, p
->zText
, p
->nChar
);
1567 mysqlStrAccumReset(p
);
1572 p
->mallocFailed
= true;
1573 mysqlStrAccumReset(p
);
1578 bool isLike
= false;
1579 std::string
testString(z
, N
);
1580 if (testString
.find("LIKE") != std::string::npos
|| testString
.find("like") != std::string::npos
)
1583 "This query part contains a like, we will double backslash in the next field: {}",
1588 memcpy(&p
->zText
[p
->nChar
], z
, N
);
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
)
1602 p
->zText
[p
->nChar
] = 0;
1603 if (p
->zText
== p
->zBase
)
1605 p
->zText
= (char*)malloc(p
->nChar
+ 1);
1608 memcpy(p
->zText
, p
->zBase
, p
->nChar
+ 1);
1612 p
->mallocFailed
= true;
1620 ** Reset an StrAccum string. Reclaim all malloced memory.
1622 void MysqlDatabase::mysqlStrAccumReset(StrAccum
* p
)
1624 if (p
->zText
!= p
->zBase
)
1632 ** Initialize a string accumulator
1634 void MysqlDatabase::mysqlStrAccumInit(StrAccum
* p
, char* zBase
, int n
, int mx
)
1636 p
->zText
= p
->zBase
= zBase
;
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
];
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
);
1661 //************* MysqlDataset implementation ***************
1663 MysqlDataset::MysqlDataset() : Dataset()
1667 autorefresh
= false;
1670 MysqlDataset::MysqlDataset(MysqlDatabase
* newDb
) : Dataset(newDb
)
1674 autorefresh
= false;
1677 MysqlDataset::~MysqlDataset()
1681 void MysqlDataset::set_autorefresh(bool val
)
1686 //--------- protected functions implementation -----------------//
1688 MYSQL
* MysqlDataset::handle()
1692 return static_cast<MysqlDatabase
*>(db
)->getHandle();
1698 void MysqlDataset::make_query(StringList
& _sql
)
1702 throw DbErrors("No Database Connection");
1706 db
->start_transaction();
1708 for (const std::string
& i
: _sql
)
1711 Dataset::parse_sql(query
);
1712 if ((static_cast<MysqlDatabase
*>(db
)->query_with_reconnect(query
.c_str())) != MYSQL_OK
)
1714 throw DbErrors(db
->getErrorMsg());
1718 if (db
->in_transaction() && autocommit
)
1719 db
->commit_transaction();
1722 ds_state
= dsSelect
;
1728 if (db
->in_transaction())
1729 db
->rollback_transaction();
1734 void MysqlDataset::make_insert()
1736 make_query(insert_sql
);
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
))
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
});
1769 if (result
.records
.size() != 0)
1771 const sql_record
* row
= result
.records
[frecno
];
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
);
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
)
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
))
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
)
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
;
1824 return loc
- where
.begin();
1827 int MysqlDataset::exec(const std::string
& sql
)
1830 throw DbErrors("No Database Connection");
1831 std::string qry
= sql
;
1835 // enforce the "auto_increment" keyword to be appended to "integer primary key"
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");
1855 qry
+= " CHARACTER SET utf8 COLLATE utf8_general_ci";
1858 const auto start
= std::chrono::steady_clock::now();
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());
1874 //! @todo collect results and store in exec_res
1879 int MysqlDataset::exec()
1884 const void* MysqlDataset::getExecRes()
1889 bool MysqlDataset::query(const std::string
& query
)
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!");
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()) !=
1912 throw DbErrors(db
->getErrorMsg());
1914 MYSQL
* conn
= handle();
1915 stmt
= mysql_store_result(conn
);
1917 throw DbErrors("Missing result set!");
1920 const unsigned int numColumns
= mysql_num_fields(stmt
);
1921 MYSQL_FIELD
* fields
= mysql_fetch_fields(stmt
);
1923 result
.record_header
.resize(numColumns
);
1924 for (unsigned int i
= 0; i
< numColumns
; i
++)
1925 result
.record_header
[i
].name
= fields
[i
].name
;
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));
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
:
1955 v
.set_asInt(atoi(row
[i
]));
1962 case MYSQL_TYPE_FLOAT
:
1963 case MYSQL_TYPE_DOUBLE
:
1966 v
.set_asDouble(atof(row
[i
]));
1973 case MYSQL_TYPE_STRING
:
1974 case MYSQL_TYPE_VAR_STRING
:
1975 case MYSQL_TYPE_VARCHAR
:
1977 v
.set_asString((const char*)row
[i
]);
1979 case MYSQL_TYPE_TINY_BLOB
:
1980 case MYSQL_TYPE_MEDIUM_BLOB
:
1981 case MYSQL_TYPE_LONG_BLOB
:
1982 case MYSQL_TYPE_BLOB
:
1984 v
.set_asString((const char*)row
[i
]);
1986 case MYSQL_TYPE_NULL
:
1988 CLog::Log(LOGDEBUG
, "MYSQL: Unknown field type: {}", fields
[i
].type
);
1994 result
.records
.push_back(res
);
1996 mysql_free_result(stmt
);
1998 ds_state
= dsSelect
;
2003 void MysqlDataset::open(const std::string
& sql
)
2005 set_select_sql(sql
);
2009 void MysqlDataset::open()
2011 if (select_sql
.size())
2017 ds_state
= dsInactive
;
2021 void MysqlDataset::close()
2025 edit_object
->clear();
2026 fields_object
->clear();
2027 ds_state
= dsInactive
;
2031 void MysqlDataset::cancel()
2033 if ((ds_state
== dsInsert
) || (ds_state
== dsEdit
))
2035 if (result
.record_header
.size())
2036 ds_state
= dsSelect
;
2038 ds_state
= dsInactive
;
2042 int MysqlDataset::num_rows()
2044 return result
.records
.size();
2047 bool MysqlDataset::eof()
2052 bool MysqlDataset::bof()
2057 void MysqlDataset::first()
2060 this->fill_fields();
2063 void MysqlDataset::last()
2069 void MysqlDataset::prev(void)
2075 void MysqlDataset::next(void)
2082 void MysqlDataset::free_row(void)
2084 if (frecno
< 0 || (unsigned int)frecno
>= result
.records
.size())
2087 sql_record
* row
= result
.records
[frecno
];
2091 result
.records
[frecno
] = NULL
;
2095 bool MysqlDataset::seek(int pos
)
2097 if (ds_state
== dsSelect
)
2107 int64_t MysqlDataset::lastinsertid()
2110 throw DbErrors("No Database Connection");
2111 return mysql_insert_id(handle());
2114 long MysqlDataset::nextid(const char* seq_name
)
2117 return db
->nextid(seq_name
);
2119 return DB_UNEXPECTED_RESULT
;
2122 void MysqlDataset::interrupt()
2127 } // namespace dbiplus