1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 Kern Sibbald
5 
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8 
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13 
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16 
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  * Bacula Catalog Database routines specific to MySQL
21  *   These are MySQL specific routines -- hopefully all
22  *    other files are generic.
23  *
24  *    Written by Kern Sibbald, March 2000
25  *
26  * Note: at one point, this file was changed to class based by a certain
27  *  programmer, and other than "wrapping" in a class, which is a trivial
28  *  change for a C++ programmer, nothing substantial was done, yet all the
29  *  code was recommitted under this programmer's name.  Consequently, we
30  *  undo those changes here.
31  *
32  */
33 
34 #include "bacula.h"
35 
36 #ifdef HAVE_MYSQL
37 
38 #include "cats.h"
39 #include <mysql.h>
40 #include <mysqld_error.h>
41 #define  __BDB_MYSQL_H_ 1
42 #include "bdb_mysql.h"
43 
44 /* -----------------------------------------------------------------------
45  *
46  *   MySQL dependent defines and subroutines
47  *
48  * -----------------------------------------------------------------------
49  */
50 
51 /* List of open databases */
52 static dlist *db_list = NULL;
53 
54 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
55 
BDB_MYSQL()56 BDB_MYSQL::BDB_MYSQL(): BDB()
57 {
58    BDB_MYSQL *mdb = this;
59 
60    if (db_list == NULL) {
61       db_list = New(dlist(this, &this->m_link));
62    }
63    mdb->m_db_driver_type = SQL_DRIVER_TYPE_MYSQL;
64    mdb->m_db_type = SQL_TYPE_MYSQL;
65    mdb->m_db_driver = bstrdup("MySQL");
66    mdb->errmsg = get_pool_memory(PM_EMSG); /* get error message buffer */
67    mdb->errmsg[0] = 0;
68    mdb->cmd = get_pool_memory(PM_EMSG);    /* get command buffer */
69    mdb->cached_path = get_pool_memory(PM_FNAME);
70    mdb->cached_path_id = 0;
71    mdb->m_ref_count = 1;
72    mdb->fname = get_pool_memory(PM_FNAME);
73    mdb->path = get_pool_memory(PM_FNAME);
74    mdb->esc_name = get_pool_memory(PM_FNAME);
75    mdb->esc_path = get_pool_memory(PM_FNAME);
76    mdb->esc_obj = get_pool_memory(PM_FNAME);
77    mdb->m_use_fatal_jmsg = true;
78 
79    /* Initialize the private members. */
80    mdb->m_db_handle = NULL;
81    mdb->m_result = NULL;
82 
83    db_list->append(this);
84 }
85 
~BDB_MYSQL()86 BDB_MYSQL::~BDB_MYSQL()
87 {
88 }
89 
90 /*
91  * Initialize database data structure. In principal this should
92  * never have errors, or it is really fatal.
93  */
db_init_database(JCR * jcr,const char * db_driver,const char * db_name,const char * db_user,const char * db_password,const char * db_address,int db_port,const char * db_socket,const char * db_ssl_mode,const char * db_ssl_key,const char * db_ssl_cert,const char * db_ssl_ca,const char * db_ssl_capath,const char * db_ssl_cipher,bool mult_db_connections,bool disable_batch_insert)94 BDB *db_init_database(JCR *jcr, const char *db_driver, const char *db_name, const char *db_user,
95                        const char *db_password, const char *db_address, int db_port, const char *db_socket,
96                        const char *db_ssl_mode, const char *db_ssl_key,
97                        const char *db_ssl_cert, const char *db_ssl_ca,
98                        const char *db_ssl_capath, const char *db_ssl_cipher,
99                        bool mult_db_connections, bool disable_batch_insert)
100 {
101    BDB_MYSQL *mdb = NULL;
102 
103    if (!db_user) {
104       Jmsg(jcr, M_FATAL, 0, _("A user name for MySQL must be supplied.\n"));
105       return NULL;
106    }
107    P(mutex);                          /* lock DB queue */
108 
109    /*
110     * Look to see if DB already open
111     */
112    if (db_list && !mult_db_connections) {
113       foreach_dlist(mdb, db_list) {
114          if (mdb->bdb_match_database(db_driver, db_name, db_address, db_port)) {
115             Dmsg1(100, "DB REopen %s\n", db_name);
116             mdb->increment_refcount();
117             goto get_out;
118          }
119       }
120    }
121    Dmsg0(100, "db_init_database first time\n");
122    mdb = New(BDB_MYSQL());
123    if (!mdb) goto get_out;
124 
125    /*
126     * Initialize the parent class members.
127     */
128    mdb->m_db_name = bstrdup(db_name);
129    mdb->m_db_user = bstrdup(db_user);
130    if (db_password) {
131       mdb->m_db_password = bstrdup(db_password);
132    }
133    if (db_address) {
134       mdb->m_db_address = bstrdup(db_address);
135    }
136    if (db_socket) {
137       mdb->m_db_socket = bstrdup(db_socket);
138    }
139    if (db_ssl_mode) {
140       mdb->m_db_ssl_mode = bstrdup(db_ssl_mode);
141    } else {
142       mdb->m_db_ssl_mode = bstrdup("preferred");
143    }
144    if (db_ssl_key) {
145       mdb->m_db_ssl_key = bstrdup(db_ssl_key);
146    }
147    if (db_ssl_cert) {
148       mdb->m_db_ssl_cert = bstrdup(db_ssl_cert);
149    }
150    if (db_ssl_ca) {
151       mdb->m_db_ssl_ca = bstrdup(db_ssl_ca);
152    }
153    if (db_ssl_capath) {
154       mdb->m_db_ssl_capath = bstrdup(db_ssl_capath);
155    }
156    if (db_ssl_cipher) {
157       mdb->m_db_ssl_cipher = bstrdup(db_ssl_cipher);
158    }
159 
160    mdb->m_db_port = db_port;
161 
162    if (disable_batch_insert) {
163       mdb->m_disabled_batch_insert = true;
164       mdb->m_have_batch_insert = false;
165    } else {
166       mdb->m_disabled_batch_insert = false;
167 #ifdef USE_BATCH_FILE_INSERT
168 #ifdef HAVE_MYSQL_THREAD_SAFE
169       mdb->m_have_batch_insert = mysql_thread_safe();
170 #else
171       mdb->m_have_batch_insert = false;
172 #endif /* HAVE_MYSQL_THREAD_SAFE */
173 #else
174       mdb->m_have_batch_insert = false;
175 #endif /* USE_BATCH_FILE_INSERT */
176    }
177 
178    mdb->m_allow_transactions = mult_db_connections;
179 
180    /* At this time, when mult_db_connections == true, this is for
181     * specific console command such as bvfs or batch mode, and we don't
182     * want to share a batch mode or bvfs. In the future, we can change
183     * the creation function to add this parameter.
184     */
185    mdb->m_dedicated = mult_db_connections;
186 
187 get_out:
188    V(mutex);
189    return mdb;
190 }
191 
is_pkey_required()192 bool BDB_MYSQL::is_pkey_required()
193 {
194    int ret = mysql_query(m_db_handle, "show variables like 'sql_require_primary_key'");
195    bool req_pkey = false;
196    if (!ret) {
197       if ((m_result = mysql_use_result(m_db_handle)) != NULL) {
198          SQL_ROW row;
199          while ((row = mysql_fetch_row(m_result))) {
200             req_pkey = !strncmp(row[1], "ON", 2);
201          }
202          sql_free_result();
203       }
204    }
205    return req_pkey;
206 }
207 
208 /*
209  * Now actually open the database.  This can generate errors,
210  *  which are returned in the errmsg
211  *
212  * DO NOT close the database or delete mdb here !!!!
213  */
bdb_open_database(JCR * jcr)214 bool BDB_MYSQL::bdb_open_database(JCR *jcr)
215 {
216    BDB_MYSQL *mdb = this;
217    bool retval = false;
218    int errstat;
219    bool reconnect = 1;
220 
221    P(mutex);
222    if (mdb->m_connected) {
223       retval = true;
224       goto get_out;
225    }
226 
227    if ((errstat=rwl_init(&mdb->m_lock)) != 0) {
228       berrno be;
229       Mmsg1(&mdb->errmsg, _("Unable to initialize DB lock. ERR=%s\n"),
230             be.bstrerror(errstat));
231       goto get_out;
232    }
233 
234    /*
235     * Connect to the database
236     */
237 #ifdef xHAVE_EMBEDDED_MYSQL
238 // mysql_server_init(0, NULL, NULL);
239 #endif
240    mysql_init(&mdb->m_instance);
241 
242    Dmsg0(50, "mysql_init done\n");
243    /*
244    * Sets the appropriate certificate options for
245    * establishing secure connection using SSL to the database.
246    */
247    if (mdb->m_db_ssl_key) {
248       mysql_ssl_set(&(mdb->m_instance),
249                    mdb->m_db_ssl_key,
250                    mdb->m_db_ssl_cert,
251                    mdb->m_db_ssl_ca,
252                    mdb->m_db_ssl_capath,
253                    mdb->m_db_ssl_cipher);
254    }
255 
256    /*
257     * If connection fails, try at 5 sec intervals for 30 seconds.
258     */
259    for (int retry=0; retry < 6; retry++) {
260       mdb->m_db_handle = mysql_real_connect(
261            &(mdb->m_instance),      /* db */
262            mdb->m_db_address,       /* default = localhost */
263            mdb->m_db_user,          /* login name */
264            mdb->m_db_password,      /* password */
265            mdb->m_db_name,          /* database name */
266            mdb->m_db_port,          /* default port */
267            mdb->m_db_socket,        /* default = socket */
268            CLIENT_FOUND_ROWS);      /* flags */
269 
270       /*
271        * If no connect, try once more in case it is a timing problem
272        */
273       if (mdb->m_db_handle != NULL) {
274          break;
275       }
276       bmicrosleep(5,0);
277    }
278 
279 #if MYSQL_VERSION_ID <= 50117
280    mysql_options(&mdb->m_instance, MYSQL_OPT_RECONNECT, (char*)&reconnect); /* so connection does not timeout */
281 #else
282    mysql_options(&mdb->m_instance, MYSQL_OPT_RECONNECT, &reconnect); /* so connection does not timeout */
283 #endif
284 
285    Dmsg0(50, "mysql_real_connect done\n");
286    Dmsg3(50, "db_user=%s db_name=%s db_password=%s\n", mdb->m_db_user, mdb->m_db_name,
287         (mdb->m_db_password == NULL) ? "(NULL)" : mdb->m_db_password);
288 
289    if (mdb->m_db_handle == NULL) {
290       Mmsg2(&mdb->errmsg, _("Unable to connect to MySQL server.\n"
291 "Database=%s User=%s\n"
292 "MySQL connect failed either server not running or your authorization is incorrect.\n"),
293          mdb->m_db_name, mdb->m_db_user);
294 #if MYSQL_VERSION_ID >= 40101
295       Dmsg3(50, "Error %u (%s): %s\n",
296             mysql_errno(&(mdb->m_instance)), mysql_sqlstate(&(mdb->m_instance)),
297             mysql_error(&(mdb->m_instance)));
298 #else
299       Dmsg2(50, "Error %u: %s\n",
300             mysql_errno(&(mdb->m_instance)), mysql_error(&(mdb->m_instance)));
301 #endif
302       goto get_out;
303    }
304    /* get the current cipher used for SSL connection */
305    if (mdb->m_db_ssl_key) {
306       const char *cipher;
307       if (mdb->m_db_ssl_cipher) {
308          free(mdb->m_db_ssl_cipher);
309       }
310       cipher = (const char *)mysql_get_ssl_cipher(&(mdb->m_instance));
311       if (cipher) {
312          mdb->m_db_ssl_cipher = bstrdup(cipher);
313       }
314       Dmsg1(50, "db_ssl_ciper=%s\n", (mdb->m_db_ssl_cipher == NULL) ? "(NULL)" : mdb->m_db_ssl_cipher);
315    }
316 
317    mdb->m_connected = true;
318    if (!bdb_check_version(jcr)) {
319       goto get_out;
320    }
321 
322    /* Check if all tables created must have a Primary Key */
323    if (is_pkey_required()) {
324      m_pkey_query_buffer = get_pool_memory(PM_FNAME);
325    } else {
326      m_pkey_query_buffer = NULL;
327    }
328 
329    Dmsg3(100, "opendb ref=%d connected=%d db=%p\n", mdb->m_ref_count, mdb->m_connected, mdb->m_db_handle);
330 
331    /*
332     * Set connection timeout to 8 days specialy for batch mode
333     */
334    sql_query("SET wait_timeout=691200");
335    sql_query("SET interactive_timeout=691200");
336 
337    retval = true;
338 
339 get_out:
340    V(mutex);
341    return retval;
342 }
343 
bdb_close_database(JCR * jcr)344 void BDB_MYSQL::bdb_close_database(JCR *jcr)
345 {
346    BDB_MYSQL *mdb = this;
347 
348    if (mdb->m_connected) {
349       bdb_end_transaction(jcr);
350    }
351    P(mutex);
352    mdb->m_ref_count--;
353    Dmsg3(100, "closedb ref=%d connected=%d db=%p\n", mdb->m_ref_count, mdb->m_connected, mdb->m_db_handle);
354    if (mdb->m_ref_count == 0) {
355       if (mdb->m_connected) {
356          sql_free_result();
357       }
358       db_list->remove(mdb);
359       if (mdb->m_connected) {
360          Dmsg1(100, "close db=%p\n", mdb->m_db_handle);
361          mysql_close(&mdb->m_instance);
362       }
363       if (is_rwl_valid(&mdb->m_lock)) {
364          rwl_destroy(&mdb->m_lock);
365       }
366       free_pool_memory(mdb->errmsg);
367       free_pool_memory(mdb->cmd);
368       free_pool_memory(mdb->cached_path);
369       free_pool_memory(mdb->fname);
370       free_pool_memory(mdb->path);
371       free_pool_memory(mdb->esc_name);
372       free_pool_memory(mdb->esc_path);
373       free_pool_memory(mdb->esc_obj);
374       if (mdb->m_pkey_query_buffer) {
375          free_pool_memory(mdb->m_pkey_query_buffer);
376       }
377       if (mdb->m_db_driver) {
378          free(mdb->m_db_driver);
379       }
380       if (mdb->m_db_name) {
381          free(mdb->m_db_name);
382       }
383       if (mdb->m_db_user) {
384          free(mdb->m_db_user);
385       }
386       if (mdb->m_db_password) {
387          free(mdb->m_db_password);
388       }
389       if (mdb->m_db_address) {
390          free(mdb->m_db_address);
391       }
392       if (mdb->m_db_socket) {
393          free(mdb->m_db_socket);
394       }
395       if (mdb->m_db_ssl_mode) {
396          free(mdb->m_db_ssl_mode);
397       }
398       if (mdb->m_db_ssl_key) {
399          free(mdb->m_db_ssl_key);
400       }
401       if (mdb->m_db_ssl_cert) {
402          free(mdb->m_db_ssl_cert);
403       }
404       if (mdb->m_db_ssl_ca) {
405          free(mdb->m_db_ssl_ca);
406       }
407       if (mdb->m_db_ssl_capath) {
408          free(mdb->m_db_ssl_capath);
409       }
410       if (mdb->m_db_ssl_cipher) {
411          free(mdb->m_db_ssl_cipher);
412       }
413       delete mdb;
414       if (db_list->size() == 0) {
415          delete db_list;
416          db_list = NULL;
417       }
418    }
419    V(mutex);
420 }
421 
422 /*
423  * This call is needed because the message channel thread
424  *  opens a database on behalf of a jcr that was created in
425  *  a different thread. MySQL then allocates thread specific
426  *  data, which is NOT freed when the original jcr thread
427  *  closes the database.  Thus the msgchan must call here
428  *  to cleanup any thread specific data that it created.
429  */
bdb_thread_cleanup(void)430 void BDB_MYSQL::bdb_thread_cleanup(void)
431 {
432 #ifndef HAVE_WIN32
433    mysql_thread_end();       /* Cleanup thread specific data */
434 #endif
435 }
436 
437 /*
438  * Escape strings so MySQL is happy
439  *
440  * len is the length of the old string. Your new
441  *   string must be long enough (max 2*old+1) to hold
442  *   the escaped output.
443  */
bdb_escape_string(JCR * jcr,char * snew,char * old,int len)444 void BDB_MYSQL::bdb_escape_string(JCR *jcr, char *snew, char *old, int len)
445 {
446    BDB_MYSQL *mdb = this;
447    mysql_real_escape_string(mdb->m_db_handle, snew, old, len);
448 }
449 
450 /*
451  * Escape binary object so that MySQL is happy
452  * Memory is stored in BDB struct, no need to free it
453  */
bdb_escape_object(JCR * jcr,char * old,int len)454 char *BDB_MYSQL::bdb_escape_object(JCR *jcr, char *old, int len)
455 {
456    BDB_MYSQL *mdb = this;
457    mdb->esc_obj = check_pool_memory_size(mdb->esc_obj, len*2+1);
458    mysql_real_escape_string(mdb->m_db_handle, mdb->esc_obj, old, len);
459    return mdb->esc_obj;
460 }
461 
462 /*
463  * Unescape binary object so that MySQL is happy
464  */
bdb_unescape_object(JCR * jcr,char * from,int32_t expected_len,POOLMEM ** dest,int32_t * dest_len)465 void BDB_MYSQL::bdb_unescape_object(JCR *jcr, char *from, int32_t expected_len,
466                                     POOLMEM **dest, int32_t *dest_len)
467 {
468    if (!from) {
469       *dest[0] = 0;
470       *dest_len = 0;
471       return;
472    }
473    *dest = check_pool_memory_size(*dest, expected_len+1);
474    *dest_len = expected_len;
475    memcpy(*dest, from, expected_len);
476    (*dest)[expected_len]=0;
477 }
478 
bdb_start_transaction(JCR * jcr)479 void BDB_MYSQL::bdb_start_transaction(JCR *jcr)
480 {
481    if (jcr) {
482       if (!jcr->attr) {
483          jcr->attr = get_pool_memory(PM_FNAME);
484       }
485       if (!jcr->ar) {
486          jcr->ar = (ATTR_DBR *)malloc(sizeof(ATTR_DBR));
487          memset(jcr->ar, 0, sizeof(ATTR_DBR));
488       }
489    }
490 }
491 
bdb_end_transaction(JCR * jcr)492 void BDB_MYSQL::bdb_end_transaction(JCR *jcr)
493 {
494 }
495 
496 /*
497  * Helper function to dynamically add Primary Keys to the tables being created.
498  */
enable_pkey(const char * query)499 const char* BDB_MYSQL::enable_pkey(const char *query)
500 {
501    if (!m_pkey_query_buffer) return query;
502    const char *tag = strstr(query, "/*PKEY");
503    if (!tag) return query;
504 
505    pm_strcpy(m_pkey_query_buffer, query);
506    char *pos = strstr(m_pkey_query_buffer, "/*PKEY");
507    for (int i=0; i<6; i++) *pos++=' ';
508    pos = strstr(pos, "*/");
509    for (int i=0; i<2; i++) *pos++=' ';
510    return m_pkey_query_buffer;
511 }
512 
513 /*
514  * Submit a general SQL command (cmd), and for each row returned,
515  * the result_handler is called with the ctx.
516  */
bdb_sql_query(const char * query,DB_RESULT_HANDLER * result_handler,void * ctx)517 bool BDB_MYSQL::bdb_sql_query(const char *query, DB_RESULT_HANDLER *result_handler, void *ctx)
518 {
519    int ret;
520    SQL_ROW row;
521    bool send = true;
522    bool retval = false;
523    BDB_MYSQL *mdb = this;
524    int retry=5;
525 
526    Dmsg1(500, "db_sql_query starts with %s\n", query);
527 
528    bdb_lock();
529    errmsg[0] = 0;
530 
531    query = enable_pkey(query);
532    do {
533       ret = mysql_query(m_db_handle, query);
534       if (ret != 0) {
535          uint32_t merrno = mysql_errno(m_db_handle);
536          switch (merrno) {
537          case ER_LOCK_DEADLOCK:
538             if (retry > 0) {
539                Dmsg0(500, "db_sql_query failed because of a deadlock, retrying in few seconds...\n");
540                bmicrosleep(2, 0);
541                /* TODO: When we will manage transactions, it's important to test if we are in or not */
542             }
543             break;
544 
545          default:
546             Dmsg1(50, "db_sql_query failed errno=%d\n", merrno);
547             retry=0;            /* Stop directly here, no retry */
548             break;
549          }
550       }
551    } while (ret != 0 && retry-- > 0);
552 
553    if (ret != 0) {
554       Mmsg(mdb->errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror());
555       Dmsg0(500, "db_sql_query failed\n");
556       goto get_out;
557    }
558 
559    Dmsg0(500, "db_sql_query succeeded. checking handler\n");
560 
561    if (result_handler) {
562       if ((mdb->m_result = mysql_use_result(mdb->m_db_handle)) != NULL) {
563          mdb->m_num_fields = mysql_num_fields(mdb->m_result);
564 
565          /*
566           * We *must* fetch all rows
567           */
568          while ((row = mysql_fetch_row(m_result))) {
569             if (send) {
570                /* the result handler returns 1 when it has
571                 *  seen all the data it wants.  However, we
572                 *  loop to the end of the data.
573                 */
574                if (result_handler(ctx, mdb->m_num_fields, row)) {
575                   send = false;
576                }
577             }
578          }
579          sql_free_result();
580       }
581    }
582 
583    Dmsg0(500, "db_sql_query finished\n");
584    retval = true;
585 
586 get_out:
587    bdb_unlock();
588    return retval;
589 }
590 
sql_query(const char * query,int flags)591 bool BDB_MYSQL::sql_query(const char *query, int flags)
592 {
593    int ret;
594    bool retval = true;
595    BDB_MYSQL *mdb = this;
596 
597    Dmsg1(500, "sql_query starts with '%s'\n", query);
598    /*
599     * We are starting a new query. reset everything.
600     */
601    mdb->m_num_rows     = -1;
602    mdb->m_row_number   = -1;
603    mdb->m_field_number = -1;
604 
605    if (mdb->m_result) {
606       mysql_free_result(mdb->m_result);
607       mdb->m_result = NULL;
608    }
609 
610    query = enable_pkey(query);
611    ret = mysql_query(mdb->m_db_handle, query);
612    if (ret == 0) {
613       Dmsg0(500, "we have a result\n");
614       if (flags & QF_STORE_RESULT) {
615          mdb->m_result = mysql_store_result(mdb->m_db_handle);
616          if (mdb->m_result != NULL) {
617             mdb->m_num_fields = mysql_num_fields(mdb->m_result);
618             Dmsg1(500, "we have %d fields\n", mdb->m_num_fields);
619             mdb->m_num_rows = mysql_num_rows(mdb->m_result);
620             Dmsg1(500, "we have %d rows\n", mdb->m_num_rows);
621          } else {
622             mdb->m_num_fields = 0;
623             mdb->m_num_rows = mysql_affected_rows(mdb->m_db_handle);
624             Dmsg1(500, "we have %d rows\n", mdb->m_num_rows);
625          }
626       } else {
627          mdb->m_num_fields = 0;
628          mdb->m_num_rows = mysql_affected_rows(mdb->m_db_handle);
629          Dmsg1(500, "we have %d rows\n", mdb->m_num_rows);
630       }
631    } else {
632       Dmsg0(500, "we failed\n");
633       mdb->m_status = 1;                   /* failed */
634       retval = false;
635    }
636    return retval;
637 }
638 
sql_free_result(void)639 void BDB_MYSQL::sql_free_result(void)
640 {
641    BDB_MYSQL *mdb = this;
642    bdb_lock();
643    if (mdb->m_result) {
644       mysql_free_result(mdb->m_result);
645       mdb->m_result = NULL;
646    }
647    if (mdb->m_fields) {
648       free(mdb->m_fields);
649       mdb->m_fields = NULL;
650    }
651    mdb->m_num_rows = mdb->m_num_fields = 0;
652    bdb_unlock();
653 }
654 
sql_fetch_row(void)655 SQL_ROW BDB_MYSQL::sql_fetch_row(void)
656 {
657    BDB_MYSQL *mdb = this;
658    if (!mdb->m_result) {
659       return NULL;
660    } else {
661       return mysql_fetch_row(mdb->m_result);
662    }
663 }
664 
sql_strerror(void)665 const char *BDB_MYSQL::sql_strerror(void)
666 {
667    BDB_MYSQL *mdb = this;
668    return mysql_error(mdb->m_db_handle);
669 }
670 
sql_data_seek(int row)671 void BDB_MYSQL::sql_data_seek(int row)
672 {
673    BDB_MYSQL *mdb = this;
674    return mysql_data_seek(mdb->m_result, row);
675 }
676 
sql_affected_rows(void)677 int BDB_MYSQL::sql_affected_rows(void)
678 {
679    BDB_MYSQL *mdb = this;
680    return mysql_affected_rows(mdb->m_db_handle);
681 }
682 
sql_insert_autokey_record(const char * query,const char * table_name)683 uint64_t BDB_MYSQL::sql_insert_autokey_record(const char *query, const char *table_name)
684 {
685    BDB_MYSQL *mdb = this;
686    /*
687     * First execute the insert query and then retrieve the currval.
688     */
689    if (mysql_query(mdb->m_db_handle, query) != 0) {
690       return 0;
691    }
692 
693    mdb->m_num_rows = mysql_affected_rows(mdb->m_db_handle);
694    if (mdb->m_num_rows != 1) {
695       return 0;
696    }
697 
698    mdb->changes++;
699 
700    return mysql_insert_id(mdb->m_db_handle);
701 }
702 
sql_fetch_field(void)703 SQL_FIELD *BDB_MYSQL::sql_fetch_field(void)
704 {
705    int i;
706    MYSQL_FIELD *field;
707    BDB_MYSQL *mdb = this;
708 
709    if (!mdb->m_fields || mdb->m_fields_size < mdb->m_num_fields) {
710       if (mdb->m_fields) {
711          free(mdb->m_fields);
712          mdb->m_fields = NULL;
713       }
714       Dmsg1(500, "allocating space for %d fields\n", mdb->m_num_fields);
715       mdb->m_fields = (SQL_FIELD *)malloc(sizeof(SQL_FIELD) * mdb->m_num_fields);
716       mdb->m_fields_size = mdb->m_num_fields;
717 
718       for (i = 0; i < mdb->m_num_fields; i++) {
719          Dmsg1(500, "filling field %d\n", i);
720          if ((field = mysql_fetch_field(mdb->m_result)) != NULL) {
721             mdb->m_fields[i].name = field->name;
722             mdb->m_fields[i].max_length = field->max_length;
723             mdb->m_fields[i].type = field->type;
724             mdb->m_fields[i].flags = field->flags;
725 
726             Dmsg4(500, "sql_fetch_field finds field '%s' has length='%d' type='%d' and IsNull=%d\n",
727                   mdb->m_fields[i].name, mdb->m_fields[i].max_length, mdb->m_fields[i].type, mdb->m_fields[i].flags);
728          }
729       }
730    }
731 
732    /*
733     * Increment field number for the next time around
734     */
735    return &mdb->m_fields[mdb->m_field_number++];
736 }
737 
sql_field_is_not_null(int field_type)738 bool BDB_MYSQL::sql_field_is_not_null(int field_type)
739 {
740    return IS_NOT_NULL(field_type);
741 }
742 
sql_field_is_numeric(int field_type)743 bool BDB_MYSQL::sql_field_is_numeric(int field_type)
744 {
745    return IS_NUM(field_type);
746 }
747 
748 /*
749  * Returns true  if OK
750  *         false if failed
751  */
sql_batch_start(JCR * jcr)752 bool BDB_MYSQL::sql_batch_start(JCR *jcr)
753 {
754    BDB_MYSQL *mdb = this;
755    bool retval;
756 
757    bdb_lock();
758    retval = sql_query("CREATE TEMPORARY TABLE batch ("
759                       "FileIndex integer not null,"
760                       "JobId integer not null,"
761                       "Path blob,"
762                       "Name blob,"
763                       "LStat tinyblob,"
764                       "MD5 tinyblob,"
765                       "DeltaSeq integer"
766                       "/*PKEY, DummyPkey INTEGER AUTO_INCREMENT PRIMARY KEY*/)");
767    bdb_unlock();
768 
769    /*
770     * Keep track of the number of changes in batch mode.
771     */
772    mdb->changes = 0;
773 
774    return retval;
775 }
776 
777 /* set error to something to abort operation */
778 /*
779  * Returns true  if OK
780  *         false if failed
781  */
sql_batch_end(JCR * jcr,const char * error)782 bool BDB_MYSQL::sql_batch_end(JCR *jcr, const char *error)
783 {
784    BDB_MYSQL *mdb = this;
785 
786    mdb->m_status = 0;
787 
788    /*
789     * Flush any pending inserts.
790     */
791    if (mdb->changes) {
792       return sql_query(mdb->cmd);
793    }
794 
795    return true;
796 }
797 
798 /*
799  * Returns true  if OK
800  *         false if failed
801  */
sql_batch_insert(JCR * jcr,ATTR_DBR * ar)802 bool BDB_MYSQL::sql_batch_insert(JCR *jcr, ATTR_DBR *ar)
803 {
804    BDB_MYSQL *mdb = this;
805    const char *digest;
806    char ed1[50];
807 
808    mdb->esc_name = check_pool_memory_size(mdb->esc_name, mdb->fnl*2+1);
809    bdb_escape_string(jcr, mdb->esc_name, mdb->fname, mdb->fnl);
810 
811    mdb->esc_path = check_pool_memory_size(mdb->esc_path, mdb->pnl*2+1);
812    bdb_escape_string(jcr, mdb->esc_path, mdb->path, mdb->pnl);
813 
814    if (ar->Digest == NULL || ar->Digest[0] == 0) {
815       digest = "0";
816    } else {
817       digest = ar->Digest;
818    }
819 
820    /*
821     * Try to batch up multiple inserts using multi-row inserts.
822     */
823    if (mdb->changes == 0) {
824       Mmsg(cmd, "INSERT INTO batch(FileIndex, JobId, Path, Name, LStat, MD5, DeltaSeq) VALUES "
825            "(%d,%s,'%s','%s','%s','%s',%u)",
826            ar->FileIndex, edit_int64(ar->JobId,ed1), mdb->esc_path,
827            mdb->esc_name, ar->attr, digest, ar->DeltaSeq);
828       mdb->changes++;
829    } else {
830       /*
831        * We use the esc_obj for temporary storage otherwise
832        * we keep on copying data.
833        */
834       Mmsg(mdb->esc_obj, ",(%d,%s,'%s','%s','%s','%s',%u)",
835            ar->FileIndex, edit_int64(ar->JobId,ed1), mdb->esc_path,
836            mdb->esc_name, ar->attr, digest, ar->DeltaSeq);
837       pm_strcat(mdb->cmd, mdb->esc_obj);
838       mdb->changes++;
839    }
840 
841    /*
842     * See if we need to flush the query buffer filled
843     * with multi-row inserts.
844     */
845    if ((mdb->changes % MYSQL_CHANGES_PER_BATCH_INSERT) == 0) {
846       if (!sql_query(mdb->cmd)) {
847          mdb->changes = 0;
848          return false;
849       } else {
850          mdb->changes = 0;
851       }
852    }
853    return true;
854 }
855 
856 
857 #endif /* HAVE_MYSQL */
858