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