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