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