]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
update version
[bacula/bacula] / bacula / src / tools / dbcheck.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2011 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *  Program to check a Bacula database for consistency and to
31  *   make repairs
32  *
33  *   Kern E. Sibbald, August 2002
34  *
35  */
36
37 #include "bacula.h"
38 #include "cats/cats.h"
39 #include "cats/sql_glue.h"
40 #include "lib/runscript.h"
41 #include "dird/dird_conf.h"
42
43 extern bool parse_dir_config(CONFIG *config, const char *configfile, int exit_code);
44
45 /*
46  * Dummy functions
47  */
48 int generate_daemon_event(JCR *jcr, const char *event)
49    { return 1; }
50
51 typedef struct s_id_ctx {
52    int64_t *Id;                       /* ids to be modified */
53    int num_ids;                       /* ids stored */
54    int max_ids;                       /* size of array */
55    int num_del;                       /* number deleted */
56    int tot_ids;                       /* total to process */
57 } ID_LIST;
58
59 typedef struct s_name_ctx {
60    char **name;                       /* list of names */
61    int num_ids;                       /* ids stored */
62    int max_ids;                       /* size of array */
63    int num_del;                       /* number deleted */
64    int tot_ids;                       /* total to process */
65 } NAME_LIST;
66
67 /*
68  * Global variables
69  */
70 static bool fix = false;
71 static bool batch = false;
72 static B_DB *db;
73 static ID_LIST id_list;
74 static NAME_LIST name_list;
75 static char buf[20000];
76 static bool quit = false;
77 static CONFIG *config;
78 static const char *idx_tmp_name;
79
80 #define MAX_ID_LIST_LEN 10000000
81
82 /*
83  * Forward referenced functions
84  */
85 static void print_catalog_details(CAT *catalog, const char *working_dir);
86 static int make_id_list(const char *query, ID_LIST *id_list);
87 static int delete_id_list(const char *query, ID_LIST *id_list);
88 static int make_name_list(const char *query, NAME_LIST *name_list);
89 static void print_name_list(NAME_LIST *name_list);
90 static void free_name_list(NAME_LIST *name_list);
91 static char *get_cmd(const char *prompt);
92 static void eliminate_duplicate_filenames();
93 static void eliminate_duplicate_paths();
94 static void eliminate_orphaned_jobmedia_records();
95 static void eliminate_orphaned_file_records();
96 static void eliminate_orphaned_path_records();
97 static void eliminate_orphaned_filename_records();
98 static void eliminate_orphaned_fileset_records();
99 static void eliminate_orphaned_client_records();
100 static void eliminate_orphaned_job_records();
101 static void eliminate_admin_records();
102 static void eliminate_restore_records();
103 static void repair_bad_paths();
104 static void repair_bad_filenames();
105 static void do_interactive_mode();
106 static bool yes_no(const char *prompt);
107 static bool check_idx(const char *col_name);
108 static bool create_tmp_idx(const char *idx_name, const char *table_name,
109               const char *col_name);
110 static bool drop_tmp_idx(const char *idx_name, const char *table_name);
111 static int check_idx_handler(void *ctx, int num_fields, char **row);
112
113 static void usage()
114 {
115    fprintf(stderr,
116 "Usage: dbcheck [-c config ] [-B] [-C catalog name] [-d debug_level] <working-directory> <bacula-database> <user> <password> [<dbhost>] [<dbport>]\n"
117 "       -b              batch mode\n"
118 "       -C              catalog name in the director conf file\n"
119 "       -c              Director conf filename\n"
120 "       -B              print catalog configuration and exit\n"
121 "       -d <nn>         set debug level to <nn>\n"
122 "       -dt             print a timestamp in debug output\n"
123 "       -f              fix inconsistencies\n"
124 "       -t              test if client library is thread-safe\n"
125 "       -v              verbose\n"
126 "       -?              print this message\n\n");
127    exit(1);
128 }
129
130 int main (int argc, char *argv[])
131 {
132    int ch;
133    const char *user, *password, *db_name, *dbhost;
134    int dbport = 0;
135    bool print_catalog=false;
136    char *configfile = NULL;
137    char *catalogname = NULL;
138    char *endptr;
139
140    setlocale(LC_ALL, "");
141    bindtextdomain("bacula", LOCALEDIR);
142    textdomain("bacula");
143    lmgr_init_thread();
144
145    my_name_is(argc, argv, "dbcheck");
146    init_msg(NULL, NULL);           /* setup message handler */
147
148    memset(&id_list, 0, sizeof(id_list));
149    memset(&name_list, 0, sizeof(name_list));
150
151    while ((ch = getopt(argc, argv, "bc:C:d:fvB?")) != -1) {
152       switch (ch) {
153       case 'B':
154          print_catalog = true;     /* get catalog information from config */
155          break;
156       case 'b':                    /* batch */
157          batch = true;
158          break;
159       case 'C':                    /* CatalogName */
160           catalogname = optarg;
161          break;
162       case 'c':                    /* configfile */
163           configfile = optarg;
164          break;
165       case 'd':                    /* debug level */
166          if (*optarg == 't') {
167             dbg_timestamp = true;
168          } else {
169             debug_level = atoi(optarg);
170             if (debug_level <= 0) {
171                debug_level = 1;
172             }
173          }
174          break;
175       case 'f':                    /* fix inconsistencies */
176          fix = true;
177          break;
178       case 'v':
179          verbose++;
180          break;
181       case '?':
182       default:
183          usage();
184       }
185    }
186    argc -= optind;
187    argv += optind;
188
189    OSDependentInit();
190
191    if (configfile) {
192       CAT *catalog = NULL;
193       int found = 0;
194       if (argc > 0) {
195          Pmsg0(0, _("Warning skipping the additional parameters for working directory/dbname/user/password/host.\n"));
196       }
197       config = new_config_parser();
198       parse_dir_config(config, configfile, M_ERROR_TERM);
199       LockRes();
200       foreach_res(catalog, R_CATALOG) {
201          if (catalogname && !strcmp(catalog->hdr.name, catalogname)) {
202             ++found;
203             break;
204          } else if (!catalogname) { // stop on first if no catalogname is given
205            ++found;
206            break;
207          }
208       }
209       UnlockRes();
210       if (!found) {
211          if (catalogname) {
212             Pmsg2(0, _("Error can not find the Catalog name[%s] in the given config file [%s]\n"), catalogname, configfile);
213          } else {
214             Pmsg1(0, _("Error there is no Catalog section in the given config file [%s]\n"), configfile);
215          }
216          exit(1);
217       } else {
218          DIRRES *director;
219          LockRes();
220          director = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
221          UnlockRes();
222          if (!director) {
223             Pmsg0(0, _("Error no Director resource defined.\n"));
224             exit(1);
225          }
226          set_working_directory(director->working_directory);
227
228          /*
229           * Print catalog information and exit (-B)
230           */
231          if (print_catalog) {
232             print_catalog_details(catalog, director->working_directory);
233             exit(0);
234          }
235
236          db_name = catalog->db_name;
237          user = catalog->db_user;
238          password = catalog->db_password;
239          dbhost = catalog->db_address;
240          if (dbhost && dbhost[0] == 0) {
241             dbhost = NULL;
242          }
243          dbport = catalog->db_port;
244       }
245    } else {
246       if (argc > 6) {
247          Pmsg0(0, _("Wrong number of arguments.\n"));
248          usage();
249       }
250
251       if (argc < 1) {
252          Pmsg0(0, _("Working directory not supplied.\n"));
253          usage();
254       }
255
256       /*
257        * This is needed by SQLite to find the db
258        */
259       working_directory = argv[0];
260       db_name = "bacula";
261       user = db_name;
262       password = "";
263       dbhost = NULL;
264
265       if (argc == 2) {
266          db_name = argv[1];
267          user = db_name;
268       } else if (argc == 3) {
269          db_name = argv[1];
270          user = argv[2];
271       } else if (argc == 4) {
272          db_name = argv[1];
273          user = argv[2];
274          password = argv[3];
275       } else if (argc == 5) {
276          db_name = argv[1];
277          user = argv[2];
278          password = argv[3];
279          dbhost = argv[4];
280       } else if (argc == 6) {
281          db_name = argv[1];
282          user = argv[2];
283          password = argv[3];
284          dbhost = argv[4];
285          errno = 0;
286          dbport = strtol(argv[5], &endptr, 10);
287          if (*endptr != '\0') {
288             Pmsg0(0, _("Database port must be a numeric value.\n"));
289             exit(1);
290          } else if (errno == ERANGE) {
291             Pmsg0(0, _("Database port must be a int value.\n"));
292             exit(1);
293          }
294       }
295    }
296
297    /*
298     * Open database
299     */
300    db = db_init_database(NULL, NULL, db_name, user, password, dbhost, dbport, NULL, false, false);
301    if (!db_open_database(NULL, db)) {
302       Emsg1(M_FATAL, 0, "%s", db_strerror(db));
303           return 1;
304    }
305
306    /*
307     * Drop temporary index idx_tmp_name if it already exists
308     */
309    drop_tmp_idx("idxPIchk", "File");
310
311    if (batch) {
312       repair_bad_paths();
313       repair_bad_filenames();
314       eliminate_duplicate_filenames();
315       eliminate_duplicate_paths();
316       eliminate_orphaned_jobmedia_records();
317       eliminate_orphaned_file_records();
318       eliminate_orphaned_path_records();
319       eliminate_orphaned_filename_records();
320       eliminate_orphaned_fileset_records();
321       eliminate_orphaned_client_records();
322       eliminate_orphaned_job_records();
323       eliminate_admin_records();
324       eliminate_restore_records();
325    } else {
326       do_interactive_mode();
327    }
328
329    /*
330     * Drop temporary index idx_tmp_name
331     */
332    drop_tmp_idx("idxPIchk", "File");
333
334    db_close_database(NULL, db);
335    close_msg(NULL);
336    term_msg();
337    lmgr_cleanup_main();
338    return 0;
339 }
340
341 static void print_catalog_details(CAT *catalog, const char *working_dir)
342 {
343    POOLMEM *catalog_details = get_pool_memory(PM_MESSAGE);
344
345    /*
346     * Instantiate a B_DB class and see what db_type gets assigned to it.
347     */
348    db = db_init_database(NULL, catalog->db_driver, catalog->db_name, catalog->db_user,
349                          catalog->db_password, catalog->db_address,
350                          catalog->db_port, catalog->db_socket,
351                          catalog->mult_db_connections,
352                          catalog->disable_batch_insert);
353    if (db) {
354       printf("%sdb_type=%s\nworking_dir=%s\n", catalog->display(catalog_details),
355              db->db_get_type(), working_directory);
356       db_close_database(NULL, db);
357    }
358    free_pool_memory(catalog_details);
359 }
360
361 static void do_interactive_mode()
362 {
363    const char *cmd;
364
365    printf(_("Hello, this is the database check/correct program.\n"));
366    if (fix)
367       printf(_("Modify database is on."));
368    else
369       printf(_("Modify database is off."));
370    if (verbose)
371       printf(_(" Verbose is on.\n"));
372    else
373       printf(_(" Verbose is off.\n"));
374
375    printf(_("Please select the function you want to perform.\n"));
376
377    while (!quit) {
378       if (fix) {
379          printf(_("\n"
380 "     1) Toggle modify database flag\n"
381 "     2) Toggle verbose flag\n"
382 "     3) Repair bad Filename records\n"
383 "     4) Repair bad Path records\n"
384 "     5) Eliminate duplicate Filename records\n"
385 "     6) Eliminate duplicate Path records\n"
386 "     7) Eliminate orphaned Jobmedia records\n"
387 "     8) Eliminate orphaned File records\n"
388 "     9) Eliminate orphaned Path records\n"
389 "    10) Eliminate orphaned Filename records\n"
390 "    11) Eliminate orphaned FileSet records\n"
391 "    12) Eliminate orphaned Client records\n"
392 "    13) Eliminate orphaned Job records\n"
393 "    14) Eliminate all Admin records\n"
394 "    15) Eliminate all Restore records\n"
395 "    16) All (3-15)\n"
396 "    17) Quit\n"));
397        } else {
398          printf(_("\n"
399 "     1) Toggle modify database flag\n"
400 "     2) Toggle verbose flag\n"
401 "     3) Check for bad Filename records\n"
402 "     4) Check for bad Path records\n"
403 "     5) Check for duplicate Filename records\n"
404 "     6) Check for duplicate Path records\n"
405 "     7) Check for orphaned Jobmedia records\n"
406 "     8) Check for orphaned File records\n"
407 "     9) Check for orphaned Path records\n"
408 "    10) Check for orphaned Filename records\n"
409 "    11) Check for orphaned FileSet records\n"
410 "    12) Check for orphaned Client records\n"
411 "    13) Check for orphaned Job records\n"
412 "    14) Check for all Admin records\n"
413 "    15) Check for all Restore records\n"
414 "    16) All (3-15)\n"
415 "    17) Quit\n"));
416        }
417
418       cmd = get_cmd(_("Select function number: "));
419       if (cmd) {
420          int item = atoi(cmd);
421          switch (item) {
422          case 1:
423             fix = !fix;
424             if (fix)
425                printf(_("Database will be modified.\n"));
426             else
427                printf(_("Database will NOT be modified.\n"));
428             break;
429          case 2:
430             verbose = verbose?0:1;
431             if (verbose)
432                printf(_(" Verbose is on.\n"));
433             else
434                printf(_(" Verbose is off.\n"));
435             break;
436          case 3:
437             repair_bad_filenames();
438             break;
439          case 4:
440             repair_bad_paths();
441             break;
442          case 5:
443             eliminate_duplicate_filenames();
444             break;
445          case 6:
446             eliminate_duplicate_paths();
447             break;
448          case 7:
449             eliminate_orphaned_jobmedia_records();
450             break;
451          case 8:
452             eliminate_orphaned_file_records();
453             break;
454          case 9:
455             eliminate_orphaned_path_records();
456             break;
457          case 10:
458             eliminate_orphaned_filename_records();
459             break;
460          case 11:
461             eliminate_orphaned_fileset_records();
462             break;
463          case 12:
464             eliminate_orphaned_client_records();
465             break;
466          case 13:
467             eliminate_orphaned_job_records();
468             break;
469          case 14:
470             eliminate_admin_records();
471             break;
472          case 15:
473             eliminate_restore_records();
474             break;
475          case 16:
476             repair_bad_filenames();
477             repair_bad_paths();
478             eliminate_duplicate_filenames();
479             eliminate_duplicate_paths();
480             eliminate_orphaned_jobmedia_records();
481             eliminate_orphaned_file_records();
482             eliminate_orphaned_path_records();
483             eliminate_orphaned_filename_records();
484             eliminate_orphaned_fileset_records();
485             eliminate_orphaned_client_records();
486             eliminate_orphaned_job_records();
487             eliminate_admin_records();
488             eliminate_restore_records();
489             break;
490          case 17:
491             quit = true;
492             break;
493          }
494       }
495    }
496 }
497
498 static int print_name_handler(void *ctx, int num_fields, char **row)
499 {
500    if (row[0]) {
501       printf("%s\n", row[0]);
502    }
503    return 0;
504 }
505
506 static int get_name_handler(void *ctx, int num_fields, char **row)
507 {
508    POOLMEM *name = (POOLMEM *)ctx;
509
510    if (row[0]) {
511       pm_strcpy(&name, row[0]);
512    }
513    return 0;
514 }
515
516 static int print_job_handler(void *ctx, int num_fields, char **row)
517 {
518    printf(_("JobId=%s Name=\"%s\" StartTime=%s\n"),
519               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
520    return 0;
521 }
522
523 static int print_jobmedia_handler(void *ctx, int num_fields, char **row)
524 {
525    printf(_("Orphaned JobMediaId=%s JobId=%s Volume=\"%s\"\n"),
526               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
527    return 0;
528 }
529
530 static int print_file_handler(void *ctx, int num_fields, char **row)
531 {
532    printf(_("Orphaned FileId=%s JobId=%s Volume=\"%s\"\n"),
533               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
534    return 0;
535 }
536
537 static int print_fileset_handler(void *ctx, int num_fields, char **row)
538 {
539    printf(_("Orphaned FileSetId=%s FileSet=\"%s\" MD5=%s\n"),
540               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
541    return 0;
542 }
543
544 static int print_client_handler(void *ctx, int num_fields, char **row)
545 {
546    printf(_("Orphaned ClientId=%s Name=\"%s\"\n"),
547               NPRT(row[0]), NPRT(row[1]));
548    return 0;
549 }
550
551 /*
552  * Called here with each id to be added to the list
553  */
554 static int id_list_handler(void *ctx, int num_fields, char **row)
555 {
556    ID_LIST *lst = (ID_LIST *)ctx;
557
558    if (lst->num_ids == MAX_ID_LIST_LEN) {
559       return 1;
560    }
561    if (lst->num_ids == lst->max_ids) {
562       if (lst->max_ids == 0) {
563          lst->max_ids = 10000;
564          lst->Id = (int64_t *)bmalloc(sizeof(int64_t) * lst->max_ids);
565       } else {
566          lst->max_ids = (lst->max_ids * 3) / 2;
567          lst->Id = (int64_t *)brealloc(lst->Id, sizeof(int64_t) * lst->max_ids);
568       }
569    }
570    lst->Id[lst->num_ids++] = str_to_int64(row[0]);
571    return 0;
572 }
573
574 /*
575  * Construct record id list
576  */
577 static int make_id_list(const char *query, ID_LIST *id_list)
578 {
579    id_list->num_ids = 0;
580    id_list->num_del = 0;
581    id_list->tot_ids = 0;
582
583    if (!db_sql_query(db, query, id_list_handler, (void *)id_list)) {
584       printf("%s", db_strerror(db));
585       return 0;
586    }
587    return 1;
588 }
589
590 /*
591  * Delete all entries in the list
592  */
593 static int delete_id_list(const char *query, ID_LIST *id_list)
594 {
595    char ed1[50];
596    for (int i=0; i < id_list->num_ids; i++) {
597       bsnprintf(buf, sizeof(buf), query, edit_int64(id_list->Id[i], ed1));
598       if (verbose) {
599          printf(_("Deleting: %s\n"), buf);
600       }
601       db_sql_query(db, buf, NULL, NULL);
602    }
603    return 1;
604 }
605
606 /*
607  * Called here with each name to be added to the list
608  */
609 static int name_list_handler(void *ctx, int num_fields, char **row)
610 {
611    NAME_LIST *name = (NAME_LIST *)ctx;
612
613    if (name->num_ids == MAX_ID_LIST_LEN) {
614       return 1;
615    }
616    if (name->num_ids == name->max_ids) {
617       if (name->max_ids == 0) {
618          name->max_ids = 10000;
619          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
620       } else {
621          name->max_ids = (name->max_ids * 3) / 2;
622          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
623       }
624    }
625    name->name[name->num_ids++] = bstrdup(row[0]);
626    return 0;
627 }
628
629 /*
630  * Construct name list
631  */
632 static int make_name_list(const char *query, NAME_LIST *name_list)
633 {
634    name_list->num_ids = 0;
635    name_list->num_del = 0;
636    name_list->tot_ids = 0;
637
638    if (!db_sql_query(db, query, name_list_handler, (void *)name_list)) {
639       printf("%s", db_strerror(db));
640       return 0;
641    }
642    return 1;
643 }
644
645 /*
646  * Print names in the list
647  */
648 static void print_name_list(NAME_LIST *name_list)
649 {
650    for (int i=0; i < name_list->num_ids; i++) {
651       printf("%s\n", name_list->name[i]);
652    }
653 }
654
655 /*
656  * Free names in the list
657  */
658 static void free_name_list(NAME_LIST *name_list)
659 {
660    for (int i=0; i < name_list->num_ids; i++) {
661       free(name_list->name[i]);
662    }
663    name_list->num_ids = 0;
664 }
665
666 static void eliminate_duplicate_filenames()
667 {
668    const char *query;
669    char esc_name[5000];
670
671    printf(_("Checking for duplicate Filename entries.\n"));
672
673    /*
674     * Make list of duplicated names
675     */
676    query = "SELECT Name, count(Name) as Count FROM Filename GROUP BY  Name "
677            "HAVING count(Name) > 1";
678
679    if (!make_name_list(query, &name_list)) {
680       exit(1);
681    }
682    printf(_("Found %d duplicate Filename records.\n"), name_list.num_ids);
683    if (name_list.num_ids && verbose && yes_no(_("Print the list? (yes/no): "))) {
684       print_name_list(&name_list);
685    }
686    if (quit) {
687       return;
688    }
689    if (fix) {
690       /*
691        * Loop through list of duplicate names
692        */
693       for (int i=0; i<name_list.num_ids; i++) {
694          /*
695           * Get all the Ids of each name
696           */
697          db_escape_string(NULL, db, esc_name, name_list.name[i], strlen(name_list.name[i]));
698          bsnprintf(buf, sizeof(buf), "SELECT FilenameId FROM Filename WHERE Name='%s'", esc_name);
699          if (verbose > 1) {
700             printf("%s\n", buf);
701          }
702          if (!make_id_list(buf, &id_list)) {
703             exit(1);
704          }
705          if (verbose) {
706             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
707          }
708          /*
709           * Force all records to use the first id then delete the other ids
710           */
711          for (int j=1; j<id_list.num_ids; j++) {
712             char ed1[50], ed2[50];
713             bsnprintf(buf, sizeof(buf), "UPDATE File SET FilenameId=%s WHERE FilenameId=%s",
714                edit_int64(id_list.Id[0], ed1), edit_int64(id_list.Id[j], ed2));
715             if (verbose > 1) {
716                printf("%s\n", buf);
717             }
718             db_sql_query(db, buf, NULL, NULL);
719             bsnprintf(buf, sizeof(buf), "DELETE FROM Filename WHERE FilenameId=%s",
720                ed2);
721             if (verbose > 2) {
722                printf("%s\n", buf);
723             }
724             db_sql_query(db, buf, NULL, NULL);
725          }
726       }
727    }
728    free_name_list(&name_list);
729 }
730
731 static void eliminate_duplicate_paths()
732 {
733    const char *query;
734    char esc_name[5000];
735
736    printf(_("Checking for duplicate Path entries.\n"));
737
738    /*
739     * Make list of duplicated names
740     */
741    query = "SELECT Path, count(Path) as Count FROM Path "
742            "GROUP BY Path HAVING count(Path) > 1";
743
744    if (!make_name_list(query, &name_list)) {
745       exit(1);
746    }
747    printf(_("Found %d duplicate Path records.\n"), name_list.num_ids);
748    if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
749       print_name_list(&name_list);
750    }
751    if (quit) {
752       return;
753    }
754    if (fix) {
755       /*
756        * Loop through list of duplicate names
757        */
758       for (int i=0; i<name_list.num_ids; i++) {
759          /*
760           * Get all the Ids of each name
761           */
762          db_escape_string(NULL, db, esc_name, name_list.name[i], strlen(name_list.name[i]));
763          bsnprintf(buf, sizeof(buf), "SELECT PathId FROM Path WHERE Path='%s'", esc_name);
764          if (verbose > 1) {
765             printf("%s\n", buf);
766          }
767          if (!make_id_list(buf, &id_list)) {
768             exit(1);
769          }
770          if (verbose) {
771             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
772          }
773          /*
774           * Force all records to use the first id then delete the other ids
775           */
776          for (int j=1; j<id_list.num_ids; j++) {
777             char ed1[50], ed2[50];
778             bsnprintf(buf, sizeof(buf), "UPDATE File SET PathId=%s WHERE PathId=%s",
779                edit_int64(id_list.Id[0], ed1), edit_int64(id_list.Id[j], ed2));
780             if (verbose > 1) {
781                printf("%s\n", buf);
782             }
783             db_sql_query(db, buf, NULL, NULL);
784             bsnprintf(buf, sizeof(buf), "DELETE FROM Path WHERE PathId=%s", ed2);
785             if (verbose > 2) {
786                printf("%s\n", buf);
787             }
788             db_sql_query(db, buf, NULL, NULL);
789          }
790       }
791    }
792    free_name_list(&name_list);
793 }
794
795 static void eliminate_orphaned_jobmedia_records()
796 {
797    const char *query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
798                 "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
799                 "WHERE Job.JobId IS NULL LIMIT 300000";
800
801    printf(_("Checking for orphaned JobMedia entries.\n"));
802    if (!make_id_list(query, &id_list)) {
803       exit(1);
804    }
805    /*
806     * Loop doing 300000 at a time
807     */
808    while (id_list.num_ids != 0) {
809       printf(_("Found %d orphaned JobMedia records.\n"), id_list.num_ids);
810       if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
811          for (int i=0; i < id_list.num_ids; i++) {
812             char ed1[50];
813             bsnprintf(buf, sizeof(buf),
814 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
815 "WHERE JobMedia.JobMediaId=%s AND Media.MediaId=JobMedia.MediaId",
816                edit_int64(id_list.Id[i], ed1));
817             if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
818                printf("%s\n", db_strerror(db));
819             }
820          }
821       }
822       if (quit) {
823          return;
824       }
825
826       if (fix && id_list.num_ids > 0) {
827          printf(_("Deleting %d orphaned JobMedia records.\n"), id_list.num_ids);
828          delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%s", &id_list);
829       } else {
830          break;                       /* get out if not updating db */
831       }
832       if (!make_id_list(query, &id_list)) {
833          exit(1);
834       }
835    }
836 }
837
838 static void eliminate_orphaned_file_records()
839 {
840    const char *query = "SELECT File.FileId,Job.JobId FROM File "
841                 "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
842                "WHERE Job.JobId IS NULL LIMIT 300000";
843
844    printf(_("Checking for orphaned File entries. This may take some time!\n"));
845    if (verbose > 1) {
846       printf("%s\n", query);
847    }
848    if (!make_id_list(query, &id_list)) {
849       exit(1);
850    }
851    /*
852     * Loop doing 300000 at a time
853     */
854    while (id_list.num_ids != 0) {
855       printf(_("Found %d orphaned File records.\n"), id_list.num_ids);
856       if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
857          for (int i=0; i < id_list.num_ids; i++) {
858             char ed1[50];
859             bsnprintf(buf, sizeof(buf),
860 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
861 "WHERE File.FileId=%s AND File.FilenameId=Filename.FilenameId",
862                edit_int64(id_list.Id[i], ed1));
863             if (!db_sql_query(db, buf, print_file_handler, NULL)) {
864                printf("%s\n", db_strerror(db));
865             }
866          }
867       }
868       if (quit) {
869          return;
870       }
871       if (fix && id_list.num_ids > 0) {
872          printf(_("Deleting %d orphaned File records.\n"), id_list.num_ids);
873          delete_id_list("DELETE FROM File WHERE FileId=%s", &id_list);
874       } else {
875          break;                       /* get out if not updating db */
876       }
877       if (!make_id_list(query, &id_list)) {
878          exit(1);
879       }
880    }
881 }
882
883 static void eliminate_orphaned_path_records()
884 {
885    idx_tmp_name = NULL;
886    /*
887     * Check the existence of the required "one column" index
888     */
889    if (!check_idx("PathId"))  {
890       if (yes_no(_("Create temporary index? (yes/no): "))) {
891          /*
892           * create temporary index PathId
893           */
894          create_tmp_idx("idxPIchk", "File", "PathId");
895       }
896    }
897
898    const char *query = "SELECT DISTINCT Path.PathId,File.PathId FROM Path "
899                "LEFT OUTER JOIN File ON (Path.PathId=File.PathId) "
900                "WHERE File.PathId IS NULL LIMIT 300000";
901
902    printf(_("Checking for orphaned Path entries. This may take some time!\n"));
903    if (verbose > 1) {
904       printf("%s\n", query);
905    }
906    if (!make_id_list(query, &id_list)) {
907       exit(1);
908    }
909    /*
910     * Loop doing 300000 at a time
911     */
912    while (id_list.num_ids != 0) {
913       printf(_("Found %d orphaned Path records.\n"), id_list.num_ids);
914       if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
915          for (int i=0; i < id_list.num_ids; i++) {
916             char ed1[50];
917             bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%s",
918                edit_int64(id_list.Id[i], ed1));
919             db_sql_query(db, buf, print_name_handler, NULL);
920          }
921       }
922       if (quit) {
923          return;
924       }
925       if (fix && id_list.num_ids > 0) {
926          printf(_("Deleting %d orphaned Path records.\n"), id_list.num_ids);
927          delete_id_list("DELETE FROM Path WHERE PathId=%s", &id_list);
928       } else {
929          break;                       /* get out if not updating db */
930       }
931       if (!make_id_list(query, &id_list)) {
932          exit(1);
933       }
934    }
935    /*
936     * Drop temporary index idx_tmp_name
937     */
938    drop_tmp_idx("idxPIchk", "File");
939 }
940
941 static void eliminate_orphaned_filename_records()
942 {
943    idx_tmp_name = NULL;
944    /*
945     * Check the existence of the required "one column" index
946     */
947    if (!check_idx("FilenameId") )      {
948       if (yes_no(_("Create temporary index? (yes/no): "))) {
949          /*
950           * Create temporary index FilenameId
951           */
952          create_tmp_idx("idxFIchk", "File", "FilenameId");
953       }
954    }
955
956    const char *query = "SELECT Filename.FilenameId,File.FilenameId FROM Filename "
957                 "LEFT OUTER JOIN File ON (Filename.FilenameId=File.FilenameId) "
958                 "WHERE File.FilenameId IS NULL LIMIT 300000";
959
960    printf(_("Checking for orphaned Filename entries. This may take some time!\n"));
961    if (verbose > 1) {
962       printf("%s\n", query);
963    }
964    if (!make_id_list(query, &id_list)) {
965       exit(1);
966    }
967    /*
968     * Loop doing 300000 at a time
969     */
970    while (id_list.num_ids != 0) {
971       printf(_("Found %d orphaned Filename records.\n"), id_list.num_ids);
972       if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
973          for (int i=0; i < id_list.num_ids; i++) {
974             char ed1[50];
975             bsnprintf(buf, sizeof(buf), "SELECT Name FROM Filename WHERE FilenameId=%s",
976                edit_int64(id_list.Id[i], ed1));
977             db_sql_query(db, buf, print_name_handler, NULL);
978          }
979       }
980       if (quit) {
981          return;
982       }
983       if (fix && id_list.num_ids > 0) {
984          printf(_("Deleting %d orphaned Filename records.\n"), id_list.num_ids);
985          delete_id_list("DELETE FROM Filename WHERE FilenameId=%s", &id_list);
986       } else {
987          break;                       /* get out if not updating db */
988       }
989       if (!make_id_list(query, &id_list)) {
990          exit(1);
991       }
992    }
993    /*
994     * Drop temporary index idx_tmp_name
995     */
996    drop_tmp_idx("idxFIchk", "File");
997
998 }
999
1000 static void eliminate_orphaned_fileset_records()
1001 {
1002    const char *query;
1003
1004    printf(_("Checking for orphaned FileSet entries. This takes some time!\n"));
1005    query = "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
1006            "LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId) "
1007            "WHERE Job.FileSetId IS NULL";
1008    if (verbose > 1) {
1009       printf("%s\n", query);
1010    }
1011    if (!make_id_list(query, &id_list)) {
1012       exit(1);
1013    }
1014    printf(_("Found %d orphaned FileSet records.\n"), id_list.num_ids);
1015    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1016       for (int i=0; i < id_list.num_ids; i++) {
1017          char ed1[50];
1018          bsnprintf(buf, sizeof(buf), "SELECT FileSetId,FileSet,MD5 FROM FileSet "
1019                       "WHERE FileSetId=%s", edit_int64(id_list.Id[i], ed1));
1020          if (!db_sql_query(db, buf, print_fileset_handler, NULL)) {
1021             printf("%s\n", db_strerror(db));
1022          }
1023       }
1024    }
1025    if (quit) {
1026       return;
1027    }
1028    if (fix && id_list.num_ids > 0) {
1029       printf(_("Deleting %d orphaned FileSet records.\n"), id_list.num_ids);
1030       delete_id_list("DELETE FROM FileSet WHERE FileSetId=%s", &id_list);
1031    }
1032 }
1033
1034 static void eliminate_orphaned_client_records()
1035 {
1036    const char *query;
1037
1038    printf(_("Checking for orphaned Client entries.\n"));
1039    /*
1040     * In English:
1041     *   Wiffle through Client for every Client
1042     *   joining with the Job table including every Client even if
1043     *   there is not a match in Job (left outer join), then
1044     *   filter out only those where no Job points to a Client
1045     *   i.e. Job.Client is NULL
1046     */
1047    query = "SELECT Client.ClientId,Client.Name FROM Client "
1048            "LEFT OUTER JOIN Job ON (Client.ClientId=Job.ClientId) "
1049            "WHERE Job.ClientId IS NULL";
1050    if (verbose > 1) {
1051       printf("%s\n", query);
1052    }
1053    if (!make_id_list(query, &id_list)) {
1054       exit(1);
1055    }
1056    printf(_("Found %d orphaned Client records.\n"), id_list.num_ids);
1057    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1058       for (int i=0; i < id_list.num_ids; i++) {
1059          char ed1[50];
1060          bsnprintf(buf, sizeof(buf), "SELECT ClientId,Name FROM Client "
1061                       "WHERE ClientId=%s", edit_int64(id_list.Id[i], ed1));
1062          if (!db_sql_query(db, buf, print_client_handler, NULL)) {
1063             printf("%s\n", db_strerror(db));
1064          }
1065       }
1066    }
1067    if (quit) {
1068       return;
1069    }
1070    if (fix && id_list.num_ids > 0) {
1071       printf(_("Deleting %d orphaned Client records.\n"), id_list.num_ids);
1072       delete_id_list("DELETE FROM Client WHERE ClientId=%s", &id_list);
1073    }
1074 }
1075
1076 static void eliminate_orphaned_job_records()
1077 {
1078    const char *query;
1079
1080    printf(_("Checking for orphaned Job entries.\n"));
1081    /*
1082     * In English:
1083     *   Wiffle through Job for every Job
1084     *   joining with the Client table including every Job even if
1085     *   there is not a match in Client (left outer join), then
1086     *   filter out only those where no Client exists
1087     *   i.e. Client.Name is NULL
1088     */
1089    query = "SELECT Job.JobId,Job.Name FROM Job "
1090            "LEFT OUTER JOIN Client ON (Job.ClientId=Client.ClientId) "
1091            "WHERE Client.Name IS NULL";
1092    if (verbose > 1) {
1093       printf("%s\n", query);
1094    }
1095    if (!make_id_list(query, &id_list)) {
1096       exit(1);
1097    }
1098    printf(_("Found %d orphaned Job records.\n"), id_list.num_ids);
1099    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1100       for (int i=0; i < id_list.num_ids; i++) {
1101          char ed1[50];
1102          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
1103                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
1104          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
1105             printf("%s\n", db_strerror(db));
1106          }
1107       }
1108    }
1109    if (quit) {
1110       return;
1111    }
1112    if (fix && id_list.num_ids > 0) {
1113       printf(_("Deleting %d orphaned Job records.\n"), id_list.num_ids);
1114       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
1115       printf(_("Deleting JobMedia records of orphaned Job records.\n"));
1116       delete_id_list("DELETE FROM JobMedia WHERE JobId=%s", &id_list);
1117       printf(_("Deleting Log records of orphaned Job records.\n"));
1118       delete_id_list("DELETE FROM Log WHERE JobId=%s", &id_list);
1119    }
1120 }
1121
1122 static void eliminate_admin_records()
1123 {
1124    const char *query;
1125
1126    printf(_("Checking for Admin Job entries.\n"));
1127    query = "SELECT Job.JobId FROM Job "
1128            "WHERE Job.Type='D'";
1129    if (verbose > 1) {
1130       printf("%s\n", query);
1131    }
1132    if (!make_id_list(query, &id_list)) {
1133       exit(1);
1134    }
1135    printf(_("Found %d Admin Job records.\n"), id_list.num_ids);
1136    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1137       for (int i=0; i < id_list.num_ids; i++) {
1138          char ed1[50];
1139          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
1140                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
1141          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
1142             printf("%s\n", db_strerror(db));
1143          }
1144       }
1145    }
1146    if (quit) {
1147       return;
1148    }
1149    if (fix && id_list.num_ids > 0) {
1150       printf(_("Deleting %d Admin Job records.\n"), id_list.num_ids);
1151       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
1152    }
1153 }
1154
1155 static void eliminate_restore_records()
1156 {
1157    const char *query;
1158
1159    printf(_("Checking for Restore Job entries.\n"));
1160    query = "SELECT Job.JobId FROM Job "
1161            "WHERE Job.Type='R'";
1162    if (verbose > 1) {
1163       printf("%s\n", query);
1164    }
1165    if (!make_id_list(query, &id_list)) {
1166       exit(1);
1167    }
1168    printf(_("Found %d Restore Job records.\n"), id_list.num_ids);
1169    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1170       for (int i=0; i < id_list.num_ids; i++) {
1171          char ed1[50];
1172          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
1173                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
1174          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
1175             printf("%s\n", db_strerror(db));
1176          }
1177       }
1178    }
1179    if (quit) {
1180       return;
1181    }
1182    if (fix && id_list.num_ids > 0) {
1183       printf(_("Deleting %d Restore Job records.\n"), id_list.num_ids);
1184       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
1185    }
1186 }
1187
1188 static void repair_bad_filenames()
1189 {
1190    const char *query;
1191    int i;
1192
1193    printf(_("Checking for Filenames with a trailing slash\n"));
1194    query = "SELECT FilenameId,Name from Filename "
1195            "WHERE Name LIKE '%/'";
1196    if (verbose > 1) {
1197       printf("%s\n", query);
1198    }
1199    if (!make_id_list(query, &id_list)) {
1200       exit(1);
1201    }
1202    printf(_("Found %d bad Filename records.\n"), id_list.num_ids);
1203    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1204       for (i=0; i < id_list.num_ids; i++) {
1205          char ed1[50];
1206          bsnprintf(buf, sizeof(buf),
1207             "SELECT Name FROM Filename WHERE FilenameId=%s",
1208                 edit_int64(id_list.Id[i], ed1));
1209          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1210             printf("%s\n", db_strerror(db));
1211          }
1212       }
1213    }
1214    if (quit) {
1215       return;
1216    }
1217    if (fix && id_list.num_ids > 0) {
1218       POOLMEM *name = get_pool_memory(PM_FNAME);
1219       char esc_name[5000];
1220       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1221       for (i=0; i < id_list.num_ids; i++) {
1222          int len;
1223          char ed1[50];
1224          bsnprintf(buf, sizeof(buf),
1225             "SELECT Name FROM Filename WHERE FilenameId=%s",
1226                edit_int64(id_list.Id[i], ed1));
1227          if (!db_sql_query(db, buf, get_name_handler, name)) {
1228             printf("%s\n", db_strerror(db));
1229          }
1230          /*
1231           * Strip trailing slash(es)
1232           */
1233          for (len=strlen(name); len > 0 && IsPathSeparator(name[len-1]); len--)
1234             {  }
1235          if (len == 0) {
1236             len = 1;
1237             esc_name[0] = ' ';
1238             esc_name[1] = 0;
1239          } else {
1240             name[len-1] = 0;
1241             db_escape_string(NULL, db, esc_name, name, len);
1242          }
1243          bsnprintf(buf, sizeof(buf),
1244             "UPDATE Filename SET Name='%s' WHERE FilenameId=%s",
1245             esc_name, edit_int64(id_list.Id[i], ed1));
1246          if (verbose > 1) {
1247             printf("%s\n", buf);
1248          }
1249          db_sql_query(db, buf, NULL, NULL);
1250       }
1251       free_pool_memory(name);
1252    }
1253 }
1254
1255 static void repair_bad_paths()
1256 {
1257    const char *query;
1258    int i;
1259
1260    printf(_("Checking for Paths without a trailing slash\n"));
1261    query = "SELECT PathId,Path from Path "
1262            "WHERE Path NOT LIKE '%/'";
1263    if (verbose > 1) {
1264       printf("%s\n", query);
1265    }
1266    if (!make_id_list(query, &id_list)) {
1267       exit(1);
1268    }
1269    printf(_("Found %d bad Path records.\n"), id_list.num_ids);
1270    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1271       for (i=0; i < id_list.num_ids; i++) {
1272          char ed1[50];
1273          bsnprintf(buf, sizeof(buf),
1274             "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1));
1275          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1276             printf("%s\n", db_strerror(db));
1277          }
1278       }
1279    }
1280    if (quit) {
1281       return;
1282    }
1283    if (fix && id_list.num_ids > 0) {
1284       POOLMEM *name = get_pool_memory(PM_FNAME);
1285       char esc_name[5000];
1286       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1287       for (i=0; i < id_list.num_ids; i++) {
1288          int len;
1289          char ed1[50];
1290          bsnprintf(buf, sizeof(buf),
1291             "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1));
1292          if (!db_sql_query(db, buf, get_name_handler, name)) {
1293             printf("%s\n", db_strerror(db));
1294          }
1295          /*
1296           * Strip trailing blanks
1297           */
1298          for (len=strlen(name); len > 0 && name[len-1]==' '; len--) {
1299             name[len-1] = 0;
1300          }
1301          /*
1302           * Add trailing slash
1303           */
1304          len = pm_strcat(&name, "/");
1305          db_escape_string(NULL, db, esc_name, name, len);
1306          bsnprintf(buf, sizeof(buf), "UPDATE Path SET Path='%s' WHERE PathId=%s",
1307             esc_name, edit_int64(id_list.Id[i], ed1));
1308          if (verbose > 1) {
1309             printf("%s\n", buf);
1310          }
1311          db_sql_query(db, buf, NULL, NULL);
1312       }
1313       free_pool_memory(name);
1314    }
1315 }
1316
1317 /*
1318  * Gen next input command from the terminal
1319  */
1320 static char *get_cmd(const char *prompt)
1321 {
1322    static char cmd[1000];
1323
1324    printf("%s", prompt);
1325    if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
1326       printf("\n");
1327       quit = true;
1328       return NULL;
1329    }
1330    strip_trailing_junk(cmd);
1331    return cmd;
1332 }
1333
1334 static bool yes_no(const char *prompt)
1335 {
1336    char *cmd;
1337    cmd = get_cmd(prompt);
1338    if (!cmd) {
1339       quit = true;
1340       return false;
1341    }
1342    return (strcasecmp(cmd, "yes") == 0) || (strcasecmp(cmd, _("yes")) == 0);
1343 }
1344
1345 bool python_set_prog(JCR*, char const*) { return false; }
1346
1347 /*
1348  * The code below to add indexes is needed only for MySQL, and
1349  *  that to improve the performance.
1350  */
1351
1352 #define MAXIDX          100
1353 typedef struct s_idx_list {
1354    char *key_name;
1355    int  count_key; /* how many times the index meets *key_name */
1356    int  count_col; /* how many times meets the desired column name */
1357 } IDX_LIST;
1358
1359 static IDX_LIST idx_list[MAXIDX];
1360
1361 /*
1362  * Called here with each table index to be added to the list
1363  */
1364 static int check_idx_handler(void *ctx, int num_fields, char **row)
1365 {
1366    /*
1367     * Table | Non_unique | Key_name | Seq_in_index | Column_name |...
1368     * File  |          0 | PRIMARY  |            1 | FileId      |...
1369     */
1370    char *name, *key_name, *col_name;
1371    int i, len;
1372    int found = false;
1373
1374    name = (char *)ctx;
1375    key_name = row[2];
1376    col_name = row[4];
1377    for(i = 0; (idx_list[i].key_name != NULL) && (i < MAXIDX); i++) {
1378       if (strcasecmp(idx_list[i].key_name, key_name) == 0 ) {
1379          idx_list[i].count_key++;
1380          found = true;
1381          if (strcasecmp(col_name, name) == 0) {
1382             idx_list[i].count_col++;
1383          }
1384          break;
1385       }
1386    }
1387    /*
1388     * If the new Key_name, add it to the list
1389     */
1390    if (!found) {
1391       len = strlen(key_name) + 1;
1392       idx_list[i].key_name = (char *)malloc(len);
1393       bstrncpy(idx_list[i].key_name, key_name, len);
1394       idx_list[i].count_key = 1;
1395       if (strcasecmp(col_name, name) == 0) {
1396          idx_list[i].count_col = 1;
1397       } else {
1398          idx_list[i].count_col = 0;
1399       }
1400    }
1401    return 0;
1402 }
1403
1404 /*
1405  * Return TRUE if "one column" index over *col_name exists
1406  */
1407 static bool check_idx(const char *col_name)
1408 {
1409    int i;
1410    int found = false;
1411    const char *query = "SHOW INDEX FROM File";
1412
1413    switch (db_get_type_index(db)) {
1414    case SQL_TYPE_MYSQL:
1415       memset(&idx_list, 0, sizeof(idx_list));
1416       if (!db_sql_query(db, query, check_idx_handler, (void *)col_name)) {
1417          printf("%s\n", db_strerror(db));
1418       }
1419       for (i = 0; (idx_list[i].key_name != NULL) && (i < MAXIDX) ; i++) {
1420          /*
1421           * NOTE : if (idx_list[i].count_key > 1) then index idx_list[i].key_name is "multiple-column" index
1422           */
1423          if ((idx_list[i].count_key == 1) && (idx_list[i].count_col == 1)) {
1424             /*
1425              * "one column" index over *col_name found
1426              */
1427             found = true;
1428          }
1429       }
1430       if (found) {
1431          if (verbose) {
1432             printf(_("Ok. Index over the %s column already exists and dbcheck will work faster.\n"), col_name);
1433          }
1434       } else {
1435          printf(_("Note. Index over the %s column not found, that can greatly slow down dbcheck.\n"), col_name);
1436       }
1437       return found;
1438    default:
1439       return true;
1440    }
1441 }
1442
1443 /*
1444  * Create temporary one-column index
1445  */
1446 static bool create_tmp_idx(const char *idx_name, const char *table_name,
1447                            const char *col_name)
1448 {
1449    idx_tmp_name = NULL;
1450    printf(_("Create temporary index... This may take some time!\n"));
1451    bsnprintf(buf, sizeof(buf), "CREATE INDEX %s ON %s (%s)", idx_name, table_name, col_name);
1452    if (verbose) {
1453       printf("%s\n", buf);
1454    }
1455    if (db_sql_query(db, buf, NULL, NULL)) {
1456       idx_tmp_name = idx_name;
1457       if (verbose) {
1458          printf(_("Temporary index created.\n"));
1459       }
1460    } else {
1461       printf("%s\n", db_strerror(db));
1462       return false;
1463    }
1464    return true;
1465 }
1466
1467 /*
1468  * Drop temporary index
1469  */
1470 static bool drop_tmp_idx(const char *idx_name, const char *table_name)
1471 {
1472    if (idx_tmp_name != NULL) {
1473       printf(_("Drop temporary index.\n"));
1474       bsnprintf(buf, sizeof(buf), "DROP INDEX %s ON %s", idx_name, table_name);
1475       if (verbose) {
1476          printf("%s\n", buf);
1477       }
1478       if (!db_sql_query(db, buf, NULL, NULL)) {
1479          printf("%s\n", db_strerror(db));
1480          return false;
1481       } else {
1482          if (verbose) {
1483             printf(_("Temporary index %s deleted.\n"), idx_tmp_name);
1484          }
1485       }
1486    }
1487    idx_tmp_name = NULL;
1488    return true;
1489 }