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...");
314 // ensure we're connected to the db we are about to copy
315 if ((ret
= mysql_select_db(conn
, db
.c_str())) != MYSQL_OK
)
316 throw DbErrors("Can't connect to source database: '%s'", db
.c_str());
318 // grab a list of base tables only (no views)
319 snprintf(sql
, sizeof(sql
), "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'");
320 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
321 throw DbErrors("Can't determine base tables for copy.");
323 // get list of all tables from old DB
324 MYSQL_RES
* res
= mysql_store_result(conn
);
328 if (mysql_num_rows(res
) == 0)
330 mysql_free_result(res
);
331 throw DbErrors("The source database was unexpectedly empty.");
334 // create the new database
335 snprintf(sql
, sizeof(sql
), "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci",
337 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
339 mysql_free_result(res
);
340 throw DbErrors("Can't create database for copy: '%s' (%d)", db
.c_str(), ret
);
345 // duplicate each table from old db to new db
346 while ((row
= mysql_fetch_row(res
)) != NULL
)
348 // copy the table definition
349 snprintf(sql
, sizeof(sql
), "CREATE TABLE `%s`.%s LIKE %s", backup_name
, row
[0], row
[0]);
351 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
353 mysql_free_result(res
);
354 throw DbErrors("Can't copy schema for table '%s'\nError: %d", row
[0], ret
);
357 // copy the table data
358 snprintf(sql
, sizeof(sql
), "INSERT INTO `%s`.%s SELECT * FROM %s", backup_name
, row
[0],
361 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
363 mysql_free_result(res
);
364 throw DbErrors("Can't copy data for table '%s'\nError: %d", row
[0], ret
);
367 mysql_free_result(res
);
369 // we don't recreate views, indices, or triggers on copy
370 // as we'll be dropping and recreating them anyway
376 int MysqlDatabase::drop_analytics(void)
378 if (!active
|| conn
== NULL
)
379 throw DbErrors("Can't clean database: no active connection...");
384 // ensure we're connected to the db we are about to clean from stuff
385 if ((ret
= mysql_select_db(conn
, db
.c_str())) != MYSQL_OK
)
386 throw DbErrors("Can't connect to database: '%s'", db
.c_str());
388 // getting a list of indexes in the database
389 snprintf(sql
, sizeof(sql
),
390 "SELECT DISTINCT table_name, index_name "
391 "FROM information_schema.statistics "
392 "WHERE index_name != 'PRIMARY' AND table_schema = '%s'",
394 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
395 throw DbErrors("Can't determine list of indexes to drop.");
397 // we will acquire lists here
398 MYSQL_RES
* res
= mysql_store_result(conn
);
403 while ((row
= mysql_fetch_row(res
)) != NULL
)
405 snprintf(sql
, sizeof(sql
), "ALTER TABLE `%s`.%s DROP INDEX %s", db
.c_str(), row
[0], row
[1]);
407 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
409 mysql_free_result(res
);
410 throw DbErrors("Can't drop index '%s'\nError: %d", row
[0], ret
);
413 mysql_free_result(res
);
416 // next topic is a views list
417 snprintf(sql
, sizeof(sql
),
418 "SELECT table_name FROM information_schema.views WHERE table_schema = '%s'", db
.c_str());
419 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
420 throw DbErrors("Can't determine list of views to drop.");
422 res
= mysql_store_result(conn
);
426 while ((row
= mysql_fetch_row(res
)) != NULL
)
428 /* we do not need IF EXISTS because these views are exist */
429 snprintf(sql
, sizeof(sql
), "DROP VIEW `%s`.%s", db
.c_str(), row
[0]);
431 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
433 mysql_free_result(res
);
434 throw DbErrors("Can't drop view '%s'\nError: %d", row
[0], ret
);
437 mysql_free_result(res
);
441 snprintf(sql
, sizeof(sql
),
442 "SELECT trigger_name FROM information_schema.triggers WHERE event_object_schema = '%s'",
444 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
445 throw DbErrors("Can't determine list of triggers to drop.");
447 res
= mysql_store_result(conn
);
451 while ((row
= mysql_fetch_row(res
)) != NULL
)
453 snprintf(sql
, sizeof(sql
), "DROP TRIGGER `%s`.%s", db
.c_str(), row
[0]);
455 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
457 mysql_free_result(res
);
458 throw DbErrors("Can't drop trigger '%s'\nError: %d", row
[0], ret
);
461 mysql_free_result(res
);
465 snprintf(sql
, sizeof(sql
),
466 "SELECT routine_name FROM information_schema.routines "
467 "WHERE routine_type = 'FUNCTION' and routine_schema = '%s'",
469 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
470 throw DbErrors("Can't determine list of routines to drop.");
472 res
= mysql_store_result(conn
);
476 while ((row
= mysql_fetch_row(res
)) != NULL
)
478 snprintf(sql
, sizeof(sql
), "DROP FUNCTION `%s`.%s", db
.c_str(), row
[0]);
480 if ((ret
= query_with_reconnect(sql
)) != MYSQL_OK
)
482 mysql_free_result(res
);
483 throw DbErrors("Can't drop function '%s'\nError: %d", row
[0], ret
);
486 mysql_free_result(res
);
492 int MysqlDatabase::query_with_reconnect(const char* query
)
497 // try to reconnect if server is gone
498 while (((result
= mysql_real_query(conn
, query
, strlen(query
))) != MYSQL_OK
) &&
499 ((result
= mysql_errno(conn
)) == CR_SERVER_GONE_ERROR
|| result
== CR_SERVER_LOST
) &&
502 CLog::Log(LOGINFO
, "MYSQL server has gone. Will try {} more attempt(s) to reconnect.",
511 long MysqlDatabase::nextid(const char* sname
)
513 CLog::Log(LOGDEBUG
, "MysqlDatabase::nextid for {}", sname
);
515 return DB_UNEXPECTED_RESULT
;
516 const char* seq_table
= "sys_seq";
517 int id
; /*,nrow,ncol;*/
520 snprintf(sqlcmd
, sizeof(sqlcmd
), "SELECT nextid FROM %s WHERE seq_name = '%s'", seq_table
, sname
);
521 CLog::Log(LOGDEBUG
, "MysqlDatabase::nextid will request");
522 if ((last_err
= query_with_reconnect(sqlcmd
)) != 0)
524 return DB_UNEXPECTED_RESULT
;
526 res
= mysql_store_result(conn
);
529 if (mysql_num_rows(res
) == 0)
532 snprintf(sqlcmd
, sizeof(sqlcmd
), "INSERT INTO %s (nextid,seq_name) VALUES (%d,'%s')",
533 seq_table
, id
, sname
);
534 mysql_free_result(res
);
535 if ((last_err
= query_with_reconnect(sqlcmd
)) != 0)
536 return DB_UNEXPECTED_RESULT
;
542 snprintf(sqlcmd
, sizeof(sqlcmd
), "UPDATE %s SET nextid=%d WHERE seq_name = '%s'", seq_table
,
544 mysql_free_result(res
);
545 if ((last_err
= query_with_reconnect(sqlcmd
)) != 0)
546 return DB_UNEXPECTED_RESULT
;
550 return DB_UNEXPECTED_RESULT
;
553 // methods for transactions
554 // ---------------------------------------------
555 void MysqlDatabase::start_transaction()
559 mysql_autocommit(conn
, false);
560 CLog::Log(LOGDEBUG
, "Mysql Start transaction");
561 _in_transaction
= true;
565 void MysqlDatabase::commit_transaction()
570 mysql_autocommit(conn
, true);
571 CLog::Log(LOGDEBUG
, "Mysql commit transaction");
572 _in_transaction
= false;
576 void MysqlDatabase::rollback_transaction()
580 mysql_rollback(conn
);
581 mysql_autocommit(conn
, true);
582 CLog::Log(LOGDEBUG
, "Mysql rollback transaction");
583 _in_transaction
= false;
587 bool MysqlDatabase::exists(void)
591 if (conn
== NULL
|| mysql_ping(conn
))
593 CLog::Log(LOGERROR
, "Not connected to database, test of existence is not possible.");
597 MYSQL_RES
* result
= mysql_list_dbs(conn
, db
.c_str());
600 CLog::Log(LOGERROR
, "Database is not present, does the user has CREATE DATABASE permission");
604 ret
= (mysql_num_rows(result
) > 0);
605 mysql_free_result(result
);
607 // Check if there is some tables ( to permit user with no create database rights
610 result
= mysql_list_tables(conn
, NULL
);
612 ret
= (mysql_num_rows(result
) > 0);
614 mysql_free_result(result
);
620 // methods for formatting
621 // ---------------------------------------------
622 std::string
MysqlDatabase::vprepare(const char* format
, va_list args
)
624 std::string strFormat
= format
;
625 std::string strResult
= "";
628 // %q is the sqlite format string for %s.
629 // Any bad character, like "'", will be replaced with a proper one
631 while ((pos
= strFormat
.find("%s", pos
)) != std::string::npos
)
632 strFormat
.replace(pos
++, 2, "%q");
634 strResult
= mysql_vmprintf(strFormat
.c_str(), args
);
635 // RAND() is the mysql form of RANDOM()
637 while ((pos
= strResult
.find("RANDOM()", pos
)) != std::string::npos
)
639 strResult
.replace(pos
++, 8, "RAND()");
643 // Replace some dataypes in CAST statements:
644 // before: CAST(iFoo AS TEXT), CAST(foo AS INTEGER)
645 // after: CAST(iFoo AS CHAR), CAST(foo AS SIGNED INTEGER)
646 pos
= strResult
.find("CAST(");
647 while (pos
!= std::string::npos
)
649 size_t pos2
= strResult
.find(" AS TEXT)", pos
+ 1);
650 if (pos2
!= std::string::npos
)
651 strResult
.replace(pos2
, 9, " AS CHAR)");
654 pos2
= strResult
.find(" AS INTEGER)", pos
+ 1);
655 if (pos2
!= std::string::npos
)
656 strResult
.replace(pos2
, 12, " AS SIGNED INTEGER)");
658 pos
= strResult
.find("CAST(", pos
+ 1);
661 // Remove COLLATE NOCASE the SQLite case insensitive collation.
662 // In MySQL all tables are defined with case insensitive collation utf8_general_ci
664 while ((pos
= strResult
.find(" COLLATE NOCASE", pos
)) != std::string::npos
)
665 strResult
.erase(pos
++, 15);
667 // Remove COLLATE ALPHANUM the SQLite custom collation.
669 while ((pos
= strResult
.find(" COLLATE ALPHANUM", pos
)) != std::string::npos
)
670 strResult
.erase(pos
++, 15);
675 /* vsprintf() functionality is based on sqlite3.c functions */
678 ** Conversion types fall into various categories as defined by the
679 ** following enumeration.
681 #define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */
682 #define etFLOAT 2 /* Floating point. %f */
683 #define etEXP 3 /* Exponential notation. %e and %E */
684 #define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */
685 #define etSIZE 5 /* Return number of characters processed so far. %n */
686 #define etSTRING 6 /* Strings. %s */
687 #define etDYNSTRING 7 /* Dynamically allocated strings. %z */
688 #define etPERCENT 8 /* Percent symbol. %% */
689 #define etCHARX 9 /* Characters. %c */
690 /* The rest are extensions, not normally found in printf() */
691 #define etSQLESCAPE 10 /* Strings with '\'' doubled. Strings with '\\' escaped. %q */
692 #define etSQLESCAPE2 \
693 11 /* Strings with '\'' doubled and enclosed in '',
694 NULL pointers replaced by SQL NULL. %Q */
695 #define etPOINTER 14 /* The %p conversion */
696 #define etSQLESCAPE3 15 /* %w -> Strings with '\"' doubled */
698 #define etINVALID 0 /* Any unrecognized conversion type */
701 ** An "etByte" is an 8-bit unsigned value.
703 typedef unsigned char etByte
;
706 ** Each builtin conversion character (ex: the 'd' in "%d") is described
707 ** by an instance of the following structure
709 typedef struct et_info
710 { /* Information about each format field */
711 char fmttype
; /* The format field code letter */
712 etByte base
; /* The base for radix conversion */
713 etByte flags
; /* One or more of FLAG_ constants below */
714 etByte type
; /* Conversion paradigm */
715 etByte charset
; /* Offset into aDigits[] of the digits string */
716 etByte prefix
; /* Offset into aPrefix[] of the prefix string */
720 ** An objected used to accumulate the text of a string where we
721 ** do not necessarily know how big the string will be in the end.
725 char* zBase
; /* A base allocation. Not from malloc. */
726 char* zText
; /* The string collected so far */
727 int nChar
; /* Length of the string so far */
728 int nAlloc
; /* Amount of space allocated in zText */
729 int mxAlloc
; /* Maximum allowed string length */
730 bool mallocFailed
; /* Becomes true if any memory allocation fails */
731 bool tooBig
; /* Becomes true if string size exceeds limits */
735 ** Allowed values for et_info.flags
737 #define FLAG_SIGNED 1 /* True if the value to convert is signed */
738 #define FLAG_INTERN 2 /* True if for internal use only */
739 #define FLAG_STRING 4 /* Allow infinity precision */
742 ** The following table is searched linearly, so it is good to put the
743 ** most frequently used conversion types first.
745 static const char aDigits
[] = "0123456789ABCDEF0123456789abcdef";
746 static const char aPrefix
[] = "-x0\000X0";
748 constexpr std::array
<et_info
, 20> fmtinfo
= {{
749 {'d', 10, 1, etRADIX
, 0, 0},
750 {'s', 0, 4, etSTRING
, 0, 0},
751 {'g', 0, 1, etGENERIC
, 30, 0},
752 {'z', 0, 4, etDYNSTRING
, 0, 0},
753 {'q', 0, 4, etSQLESCAPE
, 0, 0},
754 {'Q', 0, 4, etSQLESCAPE2
, 0, 0},
755 {'w', 0, 4, etSQLESCAPE3
, 0, 0},
756 {'c', 0, 0, etCHARX
, 0, 0},
757 {'o', 8, 0, etRADIX
, 0, 2},
758 {'u', 10, 0, etRADIX
, 0, 0},
759 {'x', 16, 0, etRADIX
, 16, 1},
760 {'X', 16, 0, etRADIX
, 0, 4},
761 {'f', 0, 1, etFLOAT
, 0, 0},
762 {'e', 0, 1, etEXP
, 30, 0},
763 {'E', 0, 1, etEXP
, 14, 0},
764 {'G', 0, 1, etGENERIC
, 14, 0},
765 {'i', 10, 1, etRADIX
, 0, 0},
766 {'n', 0, 0, etSIZE
, 0, 0},
767 {'%', 0, 0, etPERCENT
, 0, 0},
768 {'p', 16, 0, etPOINTER
, 0, 1},
773 ** "*val" is a double such that 0.1 <= *val < 10.0
774 ** Return the ascii code for the leading digit of *val, then
775 ** multiply "*val" by 10.0 to renormalize.
778 ** input: *val = 3.14159
779 ** output: *val = 1.4159 function return = '3'
781 ** The counter *cnt is incremented each time. After counter exceeds
782 ** 16 (the number of significant digits in a 64-bit float) '0' is
785 char MysqlDatabase::et_getdigit(double* val
, int* cnt
)
794 *val
= (*val
- d
) * 10.0;
799 ** Append N space characters to the given string buffer.
801 void MysqlDatabase::appendSpace(StrAccum
* pAccum
, int N
)
803 static const char zSpaces
[] = " ";
804 while (N
>= (int)sizeof(zSpaces
) - 1)
806 mysqlStrAccumAppend(pAccum
, zSpaces
, sizeof(zSpaces
) - 1);
807 N
-= sizeof(zSpaces
) - 1;
811 mysqlStrAccumAppend(pAccum
, zSpaces
, N
);
815 #ifndef MYSQL_PRINT_BUF_SIZE
816 #define MYSQL_PRINT_BUF_SIZE 350
819 #define etBUFSIZE MYSQL_PRINT_BUF_SIZE /* Size of the output buffer */
822 ** The maximum length of a TEXT or BLOB in bytes. This also
823 ** limits the size of a row in a table or index.
825 ** The hard limit is the ability of a 32-bit signed integer
826 ** to count the size: 2^31-1 or 2147483647.
828 #ifndef MYSQL_MAX_LENGTH
829 #define MYSQL_MAX_LENGTH 1000000000
833 ** The root program. All variations call this core.
836 ** func This is a pointer to a function taking three arguments
837 ** 1. A pointer to anything. Same as the "arg" parameter.
838 ** 2. A pointer to the list of characters to be output
839 ** (Note, this list is NOT null terminated.)
840 ** 3. An integer number of characters to be output.
841 ** (Note: This number might be zero.)
843 ** arg This is the pointer to anything which will be passed as the
844 ** first argument to "func". Use it for whatever you like.
846 ** fmt This is the format string, as in the usual print.
848 ** ap This is a pointer to a list of arguments. Same as in
852 ** The return value is the total number of characters sent to
853 ** the function "func". Returns -1 on a error.
855 ** Note that the order in which automatic variables are declared below
856 ** seems to make a big difference in determining how fast this beast
859 void MysqlDatabase::mysqlVXPrintf(StrAccum
* pAccum
, /* Accumulate results here */
860 int useExtended
, /* Allow extended %-conversions */
861 const char* fmt
, /* Format string */
862 va_list ap
/* arguments */
865 int c
; /* Next character in the format string */
866 char* bufpt
; /* Pointer to the conversion buffer */
867 int precision
; /* Precision of the current field */
868 int length
; /* Length of the field */
869 int idx
; /* A general purpose loop counter */
870 int width
; /* Width of the current field */
871 etByte flag_leftjustify
; /* True if "-" flag is present */
872 etByte flag_plussign
; /* True if "+" flag is present */
873 etByte flag_blanksign
; /* True if " " flag is present */
874 etByte flag_alternateform
; /* True if "#" flag is present */
875 etByte flag_altform2
; /* True if "!" flag is present */
876 etByte flag_zeropad
; /* True if field width constant starts with zero */
877 etByte flag_long
; /* True if "l" flag is present */
878 etByte flag_longlong
; /* True if the "ll" flag is present */
879 etByte done
; /* Loop termination flag */
880 uint64_t longvalue
; /* Value for integer types */
881 double realvalue
; /* Value for real types */
882 const et_info
* infop
; /* Pointer to the appropriate info structure */
883 char buf
[etBUFSIZE
]; /* Conversion buffer */
884 char prefix
; /* Prefix character. "+" or "-" or " " or '\0'. */
885 etByte xtype
= 0; /* Conversion paradigm */
886 char* zExtra
; /* Extra memory used for etTCLESCAPE conversions */
887 int exp
, e2
; /* exponent of real numbers */
888 double rounder
; /* Used for rounding floating point values */
889 etByte flag_dp
; /* True if decimal point should be shown */
890 etByte flag_rtz
; /* True if trailing zeros should be removed */
891 etByte flag_exp
; /* True to force display of the exponent */
892 int nsd
; /* Number of significant digits returned */
896 for (; (c
= (*fmt
)) != 0; ++fmt
)
902 bufpt
= const_cast<char*>(fmt
);
904 while ((c
= (*++fmt
)) != '%' && c
!= 0)
906 isLike
= mysqlStrAccumAppend(pAccum
, bufpt
, amt
);
910 if ((c
= (*++fmt
)) == 0)
912 mysqlStrAccumAppend(pAccum
, "%", 1);
915 /* Find out what flags are present */
916 flag_leftjustify
= flag_plussign
= flag_blanksign
= flag_alternateform
= flag_altform2
=
924 flag_leftjustify
= 1;
933 flag_alternateform
= 1;
945 } while (!done
&& (c
= (*++fmt
)) != 0);
946 /* Get the field width */
950 width
= va_arg(ap
, int);
953 flag_leftjustify
= 1;
960 while (c
>= '0' && c
<= '9')
962 width
= width
* 10 + c
- '0';
966 if (width
> etBUFSIZE
- 10)
968 width
= etBUFSIZE
- 10;
970 /* Get the precision */
977 precision
= va_arg(ap
, int);
979 precision
= -precision
;
984 while (c
>= '0' && c
<= '9')
986 precision
= precision
* 10 + c
- '0';
995 /* Get the conversion type modifier */
1012 flag_long
= flag_longlong
= 0;
1014 /* Fetch the info entry for the field */
1015 infop
= fmtinfo
.data();
1018 for (const auto& info
: fmtinfo
)
1020 if (c
!= info
.fmttype
)
1025 if (useExtended
|| (infop
->flags
& FLAG_INTERN
) == 0)
1027 xtype
= infop
->type
;
1039 /* Limit the precision to prevent overflowing buf[] during conversion */
1040 if (precision
> etBUFSIZE
- 40 && (infop
->flags
& FLAG_STRING
) == 0)
1042 precision
= etBUFSIZE
- 40;
1046 ** At this point, variables are initialized as follows:
1048 ** flag_alternateform TRUE if a '#' is present.
1049 ** flag_altform2 TRUE if a '!' is present.
1050 ** flag_plussign TRUE if a '+' is present.
1051 ** flag_leftjustify TRUE if a '-' is present or if the
1052 ** field width was negative.
1053 ** flag_zeropad TRUE if the width began with 0.
1054 ** flag_long TRUE if the letter 'l' (ell) prefixed
1055 ** the conversion character.
1056 ** flag_longlong TRUE if the letter 'll' (ell ell) prefixed
1057 ** the conversion character.
1058 ** flag_blanksign TRUE if a ' ' is present.
1059 ** width The specified field width. This is
1060 ** always non-negative. Zero is the default.
1061 ** precision The specified precision. The default
1063 ** xtype The class of the conversion.
1064 ** infop Pointer to the appropriate info struct.
1069 flag_longlong
= sizeof(char*) == sizeof(int64_t);
1070 flag_long
= sizeof(char*) == sizeof(long int);
1071 /* Fall through into the next case */
1074 if (infop
->flags
& FLAG_SIGNED
)
1079 v
= va_arg(ap
, int64_t);
1083 v
= va_arg(ap
, long int);
1087 v
= va_arg(ap
, int);
1099 else if (flag_blanksign
)
1109 longvalue
= va_arg(ap
, uint64_t);
1113 longvalue
= va_arg(ap
, unsigned long int);
1117 longvalue
= va_arg(ap
, unsigned int);
1122 flag_alternateform
= 0;
1123 if (flag_zeropad
&& precision
< width
- (prefix
!= 0))
1125 precision
= width
- (prefix
!= 0);
1127 bufpt
= &buf
[etBUFSIZE
- 1];
1131 cset
= &aDigits
[infop
->charset
];
1134 { /* Convert to ascii */
1135 *(--bufpt
) = cset
[longvalue
% base
];
1136 longvalue
= longvalue
/ base
;
1137 } while (longvalue
> 0);
1139 length
= (int)(&buf
[etBUFSIZE
- 1] - bufpt
);
1140 for (idx
= precision
- length
; idx
> 0; idx
--)
1142 *(--bufpt
) = '0'; /* Zero pad */
1145 *(--bufpt
) = prefix
; /* Add sign */
1146 if (flag_alternateform
&& infop
->prefix
)
1147 { /* Add "0" or "0x" */
1150 pre
= &aPrefix
[infop
->prefix
];
1151 for (; (x
= (*pre
)) != 0; pre
++)
1154 length
= (int)(&buf
[etBUFSIZE
- 1] - bufpt
);
1160 realvalue
= va_arg(ap
, double);
1162 precision
= 6; /* Set default precision */
1163 if (precision
> etBUFSIZE
/ 2 - 10)
1164 precision
= etBUFSIZE
/ 2 - 10;
1165 if (realvalue
< 0.0)
1167 realvalue
= -realvalue
;
1174 else if (flag_blanksign
)
1179 if (xtype
== etGENERIC
&& precision
> 0)
1181 /* It makes more sense to use 0.5 */
1182 for (idx
= precision
, rounder
= 0.5; idx
> 0; idx
--, rounder
*= 0.1)
1185 if (xtype
== etFLOAT
)
1186 realvalue
+= rounder
;
1187 /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
1190 if( mysqlIsNaN((double)realvalue
) ){
1196 if (realvalue
> 0.0)
1198 while (realvalue
>= 1e32
&& exp
<= 350)
1203 while (realvalue
>= 1e8
&& exp
<= 350)
1208 while (realvalue
>= 10.0 && exp
<= 350)
1213 while (realvalue
< 1e-8)
1218 while (realvalue
< 1.0)
1227 bufpt
= const_cast<char*>("-Inf");
1229 else if (prefix
== '+')
1231 bufpt
= const_cast<char*>("+Inf");
1235 bufpt
= const_cast<char*>("Inf");
1237 length
= strlen(bufpt
);
1243 ** If the field type is etGENERIC, then convert to either etEXP
1244 ** or etFLOAT, as appropriate.
1246 flag_exp
= xtype
== etEXP
;
1247 if (xtype
!= etFLOAT
)
1249 realvalue
+= rounder
;
1250 if (realvalue
>= 10.0)
1256 if (xtype
== etGENERIC
)
1258 flag_rtz
= !flag_alternateform
;
1259 if (exp
< -4 || exp
> precision
)
1265 precision
= precision
- exp
;
1282 flag_dp
= (precision
> 0 ? 1 : 0) | flag_alternateform
| flag_altform2
;
1283 /* The sign in front of the number */
1286 *(bufpt
++) = prefix
;
1288 /* Digits prior to the decimal point */
1295 for (; e2
>= 0; e2
--)
1297 *(bufpt
++) = et_getdigit(&realvalue
, &nsd
);
1300 /* The decimal point */
1305 /* "0" digits after the decimal point but before the first
1306 ** significant digit of the number */
1307 for (e2
++; e2
< 0; precision
--, e2
++)
1309 //ASSERT( precision>0 );
1312 /* Significant digits after the decimal point */
1313 while ((precision
--) > 0)
1315 *(bufpt
++) = et_getdigit(&realvalue
, &nsd
);
1317 /* Remove trailing zeros and the "." if no digits follow the "." */
1318 if (flag_rtz
&& flag_dp
)
1320 while (bufpt
[-1] == '0')
1322 //ASSERT( bufpt>buf );
1323 if (bufpt
[-1] == '.')
1335 /* Add the "eNNN" suffix */
1336 if (flag_exp
|| xtype
== etEXP
)
1338 *(bufpt
++) = aDigits
[infop
->charset
];
1350 *(bufpt
++) = (char)((exp
/ 100) + '0'); /* 100's digit */
1353 *(bufpt
++) = (char)(exp
/ 10 + '0'); /* 10's digit */
1354 *(bufpt
++) = (char)(exp
% 10 + '0'); /* 1's digit */
1358 /* The converted number is in buf[] and zero terminated. Output it.
1359 ** Note that the number is in the usual order, not reversed as with
1360 ** integer conversions. */
1361 length
= (int)(bufpt
- buf
);
1364 /* Special case: Add leading zeros if the flag_zeropad flag is
1365 ** set and we are not left justified */
1366 if (flag_zeropad
&& !flag_leftjustify
&& length
< width
)
1369 int nPad
= width
- length
;
1370 for (i
= width
; i
>= nPad
; i
--)
1372 bufpt
[i
] = bufpt
[i
- nPad
];
1381 *(va_arg(ap
, int*)) = pAccum
->nChar
;
1390 c
= va_arg(ap
, int);
1394 for (idx
= 1; idx
< precision
; idx
++)
1406 bufpt
= va_arg(ap
, char*);
1409 bufpt
= const_cast<char*>("");
1411 else if (xtype
== etDYNSTRING
)
1417 for (length
= 0; length
< precision
&& bufpt
[length
]; length
++)
1423 length
= strlen(bufpt
);
1430 int i
, j
, k
, n
, isnull
;
1433 char q
= ((xtype
== etSQLESCAPE3
) ? '"' : '\''); /* Quote character */
1434 std::string arg
= va_arg(ap
, char*);
1436 StringUtils::Replace(arg
, "\\", "\\\\");
1437 const char* escarg
= arg
.c_str();
1439 isnull
= escarg
== 0;
1441 escarg
= (xtype
== etSQLESCAPE2
? "NULL" : "(NULL)");
1443 for (i
= 0; k
!= 0 && (ch
= escarg
[i
]) != 0; i
++, k
--)
1445 needQuote
= !isnull
&& xtype
== etSQLESCAPE2
;
1446 n
= i
* 2 + 1 + needQuote
* 2;
1449 bufpt
= zExtra
= (char*)malloc(n
);
1452 pAccum
->mallocFailed
= true;
1464 j
+= mysql_real_escape_string(conn
, bufpt
, escarg
, k
);
1469 /* The precision in %q and %Q means how many input characters to
1470 ** consume, not the length of the output...
1471 ** if( precision>=0 && precision<length ) length = precision; */
1478 } /* End switch over the format type */
1480 ** The text of the conversion is pointed to by "bufpt" and is
1481 ** "length" characters long. The field width is "width". Do
1484 if (!flag_leftjustify
)
1487 nspace
= width
- length
;
1490 appendSpace(pAccum
, nspace
);
1495 mysqlStrAccumAppend(pAccum
, bufpt
, length
);
1497 if (flag_leftjustify
)
1500 nspace
= width
- length
;
1503 appendSpace(pAccum
, nspace
);
1510 } /* End for loop over the format string */
1511 } /* End of function */
1514 ** Append N bytes of text from z to the StrAccum object.
1516 bool MysqlDatabase::mysqlStrAccumAppend(StrAccum
* p
, const char* z
, int N
)
1518 if (p
->tooBig
| p
->mallocFailed
)
1526 if (N
== 0 || z
== 0)
1530 if (p
->nChar
+ N
>= p
->nAlloc
)
1533 int szNew
= p
->nChar
;
1535 if (szNew
> p
->mxAlloc
)
1537 mysqlStrAccumReset(p
);
1545 zNew
= (char*)malloc(p
->nAlloc
);
1548 memcpy(zNew
, p
->zText
, p
->nChar
);
1549 mysqlStrAccumReset(p
);
1554 p
->mallocFailed
= true;
1555 mysqlStrAccumReset(p
);
1560 bool isLike
= false;
1561 std::string
testString(z
, N
);
1562 if (testString
.find("LIKE") != std::string::npos
|| testString
.find("like") != std::string::npos
)
1565 "This query part contains a like, we will double backslash in the next field: {}",
1570 memcpy(&p
->zText
[p
->nChar
], z
, N
);
1576 ** Finish off a string by making sure it is zero-terminated.
1577 ** Return a pointer to the resulting string. Return a NULL
1578 ** pointer if any kind of error was encountered.
1580 char* MysqlDatabase::mysqlStrAccumFinish(StrAccum
* p
)
1584 p
->zText
[p
->nChar
] = 0;
1585 if (p
->zText
== p
->zBase
)
1587 p
->zText
= (char*)malloc(p
->nChar
+ 1);
1590 memcpy(p
->zText
, p
->zBase
, p
->nChar
+ 1);
1594 p
->mallocFailed
= true;
1602 ** Reset an StrAccum string. Reclaim all malloced memory.
1604 void MysqlDatabase::mysqlStrAccumReset(StrAccum
* p
)
1606 if (p
->zText
!= p
->zBase
)
1614 ** Initialize a string accumulator
1616 void MysqlDatabase::mysqlStrAccumInit(StrAccum
* p
, char* zBase
, int n
, int mx
)
1618 p
->zText
= p
->zBase
= zBase
;
1623 p
->mallocFailed
= false;
1627 ** Print into memory obtained from mysql_malloc(). Omit the internal
1628 ** %-conversion extensions.
1630 std::string
MysqlDatabase::mysql_vmprintf(const char* zFormat
, va_list ap
)
1632 char zBase
[MYSQL_PRINT_BUF_SIZE
];
1635 mysqlStrAccumInit(&acc
, zBase
, sizeof(zBase
), MYSQL_MAX_LENGTH
);
1636 mysqlVXPrintf(&acc
, 0, zFormat
, ap
);
1637 return mysqlStrAccumFinish(&acc
);
1640 //************* MysqlDataset implementation ***************
1642 MysqlDataset::MysqlDataset() : Dataset()
1646 autorefresh
= false;
1649 MysqlDataset::MysqlDataset(MysqlDatabase
* newDb
) : Dataset(newDb
)
1653 autorefresh
= false;
1656 MysqlDataset::~MysqlDataset()
1660 void MysqlDataset::set_autorefresh(bool val
)
1665 //--------- protected functions implementation -----------------//
1667 MYSQL
* MysqlDataset::handle()
1671 return static_cast<MysqlDatabase
*>(db
)->getHandle();
1677 void MysqlDataset::make_query(StringList
& _sql
)
1681 throw DbErrors("No Database Connection");
1685 db
->start_transaction();
1687 for (const std::string
& i
: _sql
)
1690 Dataset::parse_sql(query
);
1691 if ((static_cast<MysqlDatabase
*>(db
)->query_with_reconnect(query
.c_str())) != MYSQL_OK
)
1693 throw DbErrors(db
->getErrorMsg());
1697 if (db
->in_transaction() && autocommit
)
1698 db
->commit_transaction();
1701 ds_state
= dsSelect
;
1707 if (db
->in_transaction())
1708 db
->rollback_transaction();
1713 void MysqlDataset::make_insert()
1715 make_query(insert_sql
);
1719 void MysqlDataset::make_edit()
1721 make_query(update_sql
);
1724 void MysqlDataset::make_deletion()
1726 make_query(delete_sql
);
1729 void MysqlDataset::fill_fields()
1731 if ((db
== NULL
) || (result
.record_header
.empty()) ||
1732 (result
.records
.size() < (unsigned int)frecno
))
1735 if (fields_object
->size() == 0) // Filling columns name
1737 const unsigned int ncols
= result
.record_header
.size();
1738 fields_object
->resize(ncols
);
1739 for (unsigned int i
= 0; i
< ncols
; i
++)
1741 (*fields_object
)[i
].props
= result
.record_header
[i
];
1742 std::string name
= result
.record_header
[i
].name
;
1743 name2indexMap
.insert({str_toLower(name
.data()), i
});
1748 if (result
.records
.size() != 0)
1750 const sql_record
* row
= result
.records
[frecno
];
1753 const unsigned int ncols
= row
->size();
1754 fields_object
->resize(ncols
);
1755 for (unsigned int i
= 0; i
< ncols
; i
++)
1756 (*fields_object
)[i
].val
= row
->at(i
);
1760 const unsigned int ncols
= result
.record_header
.size();
1761 fields_object
->resize(ncols
);
1762 for (unsigned int i
= 0; i
< ncols
; i
++)
1763 (*fields_object
)[i
].val
= "";
1766 //------------- public functions implementation -----------------//
1767 bool MysqlDataset::dropIndex(const char* table
, const char* index
)
1770 std::string sql_prepared
;
1772 sql
= "SELECT * FROM information_schema.statistics WHERE TABLE_SCHEMA=DATABASE() AND "
1773 "table_name='%s' AND index_name='%s'";
1774 sql_prepared
= static_cast<MysqlDatabase
*>(db
)->prepare(sql
.c_str(), table
, index
);
1776 if (!query(sql_prepared
))
1781 sql
= "ALTER TABLE %s DROP INDEX %s";
1782 sql_prepared
= static_cast<MysqlDatabase
*>(db
)->prepare(sql
.c_str(), table
, index
);
1784 if (exec(sql_prepared
) != MYSQL_OK
)
1791 static bool ci_test(char l
, char r
)
1793 return tolower(l
) == tolower(r
);
1796 static size_t ci_find(const std::string
& where
, const std::string
& what
)
1798 std::string::const_iterator loc
=
1799 std::search(where
.begin(), where
.end(), what
.begin(), what
.end(), ci_test
);
1800 if (loc
== where
.end())
1801 return std::string::npos
;
1803 return loc
- where
.begin();
1806 int MysqlDataset::exec(const std::string
& sql
)
1809 throw DbErrors("No Database Connection");
1810 std::string qry
= sql
;
1814 // enforce the "auto_increment" keyword to be appended to "integer primary key"
1817 if ((loc
= ci_find(qry
, "integer primary key")) != std::string::npos
)
1819 qry
= qry
.insert(loc
+ 19, " auto_increment ");
1822 // force the charset and collation to UTF-8
1823 if (ci_find(qry
, "CREATE TABLE") != std::string::npos
||
1824 ci_find(qry
, "CREATE TEMPORARY TABLE") != std::string::npos
)
1826 // If CREATE TABLE ... SELECT Syntax is used we need to add the encoding after the table before the select
1827 // e.g. CREATE TABLE x CHARACTER SET utf8 COLLATE utf8_general_ci [AS] SELECT * FROM y
1828 if ((loc
= qry
.find(" AS SELECT ")) != std::string::npos
||
1829 (loc
= qry
.find(" SELECT ")) != std::string::npos
)
1831 qry
= qry
.insert(loc
, " CHARACTER SET utf8 COLLATE utf8_general_ci");
1834 qry
+= " CHARACTER SET utf8 COLLATE utf8_general_ci";
1837 CLog::Log(LOGDEBUG
, "Mysql execute: {}", qry
);
1839 if (db
->setErr(static_cast<MysqlDatabase
*>(db
)->query_with_reconnect(qry
.c_str()), qry
.c_str()) !=
1842 throw DbErrors(db
->getErrorMsg());
1846 //! @todo collect results and store in exec_res
1851 int MysqlDataset::exec()
1856 const void* MysqlDataset::getExecRes()
1861 bool MysqlDataset::query(const std::string
& query
)
1864 throw DbErrors("No Database Connection");
1865 std::string qry
= query
;
1866 int fs
= qry
.find("select");
1867 int fS
= qry
.find("SELECT");
1868 if (!(fs
>= 0 || fS
>= 0))
1869 throw DbErrors("MUST be select SQL!");
1875 // mysql doesn't understand CAST(foo as integer) => change to CAST(foo as signed integer)
1876 while ((loc
= ci_find(qry
, "as integer)")) != std::string::npos
)
1877 qry
= qry
.insert(loc
+ 3, "signed ");
1879 MYSQL_RES
* stmt
= NULL
;
1881 if (static_cast<MysqlDatabase
*>(db
)->setErr(
1882 static_cast<MysqlDatabase
*>(db
)->query_with_reconnect(qry
.c_str()), qry
.c_str()) !=
1884 throw DbErrors(db
->getErrorMsg());
1886 MYSQL
* conn
= handle();
1887 stmt
= mysql_store_result(conn
);
1889 throw DbErrors("Missing result set!");
1892 const unsigned int numColumns
= mysql_num_fields(stmt
);
1893 MYSQL_FIELD
* fields
= mysql_fetch_fields(stmt
);
1895 result
.record_header
.resize(numColumns
);
1896 for (unsigned int i
= 0; i
< numColumns
; i
++)
1897 result
.record_header
[i
].name
= fields
[i
].name
;
1900 while ((row
= mysql_fetch_row(stmt
)))
1901 { // have a row of data
1902 sql_record
* res
= new sql_record
;
1903 res
->resize(numColumns
);
1904 for (unsigned int i
= 0; i
< numColumns
; i
++)
1906 field_value
& v
= res
->at(i
);
1907 switch (fields
[i
].type
)
1909 case MYSQL_TYPE_LONGLONG
:
1910 if (row
[i
] != nullptr)
1912 v
.set_asInt64(strtoll(row
[i
], nullptr, 10));
1919 case MYSQL_TYPE_DECIMAL
:
1920 case MYSQL_TYPE_NEWDECIMAL
:
1921 case MYSQL_TYPE_TINY
:
1922 case MYSQL_TYPE_SHORT
:
1923 case MYSQL_TYPE_INT24
:
1924 case MYSQL_TYPE_LONG
:
1927 v
.set_asInt(atoi(row
[i
]));
1934 case MYSQL_TYPE_FLOAT
:
1935 case MYSQL_TYPE_DOUBLE
:
1938 v
.set_asDouble(atof(row
[i
]));
1945 case MYSQL_TYPE_STRING
:
1946 case MYSQL_TYPE_VAR_STRING
:
1947 case MYSQL_TYPE_VARCHAR
:
1949 v
.set_asString((const char*)row
[i
]);
1951 case MYSQL_TYPE_TINY_BLOB
:
1952 case MYSQL_TYPE_MEDIUM_BLOB
:
1953 case MYSQL_TYPE_LONG_BLOB
:
1954 case MYSQL_TYPE_BLOB
:
1956 v
.set_asString((const char*)row
[i
]);
1958 case MYSQL_TYPE_NULL
:
1960 CLog::Log(LOGDEBUG
, "MYSQL: Unknown field type: {}", fields
[i
].type
);
1966 result
.records
.push_back(res
);
1968 mysql_free_result(stmt
);
1970 ds_state
= dsSelect
;
1975 void MysqlDataset::open(const std::string
& sql
)
1977 set_select_sql(sql
);
1981 void MysqlDataset::open()
1983 if (select_sql
.size())
1989 ds_state
= dsInactive
;
1993 void MysqlDataset::close()
1997 edit_object
->clear();
1998 fields_object
->clear();
1999 ds_state
= dsInactive
;
2003 void MysqlDataset::cancel()
2005 if ((ds_state
== dsInsert
) || (ds_state
== dsEdit
))
2007 if (result
.record_header
.size())
2008 ds_state
= dsSelect
;
2010 ds_state
= dsInactive
;
2014 int MysqlDataset::num_rows()
2016 return result
.records
.size();
2019 bool MysqlDataset::eof()
2024 bool MysqlDataset::bof()
2029 void MysqlDataset::first()
2032 this->fill_fields();
2035 void MysqlDataset::last()
2041 void MysqlDataset::prev(void)
2047 void MysqlDataset::next(void)
2054 void MysqlDataset::free_row(void)
2056 if (frecno
< 0 || (unsigned int)frecno
>= result
.records
.size())
2059 sql_record
* row
= result
.records
[frecno
];
2063 result
.records
[frecno
] = NULL
;
2067 bool MysqlDataset::seek(int pos
)
2069 if (ds_state
== dsSelect
)
2079 int64_t MysqlDataset::lastinsertid()
2082 throw DbErrors("No Database Connection");
2083 return mysql_insert_id(handle());
2086 long MysqlDataset::nextid(const char* seq_name
)
2089 return db
->nextid(seq_name
);
2091 return DB_UNEXPECTED_RESULT
;
2094 void MysqlDataset::interrupt()
2099 } // namespace dbiplus