2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
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
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.
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
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.
29 * Bacula Catalog Database Get record interface routines
30 * Note, these routines generally get a record by id or
31 * by name. If more logic is involved, the routine
34 * Kern Sibbald, March 2000
39 /* The following is necessary so that we do not include
40 * the dummy external definition of DB.
42 #define __SQL_C /* indicate that this is sql.c */
47 #if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI
49 /* -----------------------------------------------------------------------
51 * Generic Routines (or almost generic)
53 * -----------------------------------------------------------------------
56 /* Forward referenced functions */
57 static int db_get_file_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr, FILE_DBR *fdbr);
58 static int db_get_filename_record(JCR *jcr, B_DB *mdb);
62 * Given a full filename (with path), look up the File record
63 * (with attributes) in the database.
65 * Returns: 0 on failure
66 * 1 on success with the File record in FILE_DBR
68 int db_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JOB_DBR *jr, FILE_DBR *fdbr)
71 Dmsg1(100, "db_get_file_att_record fname=%s \n", fname);
74 split_path_and_file(jcr, mdb, fname);
76 fdbr->FilenameId = db_get_filename_record(jcr, mdb);
78 fdbr->PathId = db_get_path_record(jcr, mdb);
80 stat = db_get_file_record(jcr, mdb, jr, fdbr);
90 * Returns: 0 on failure
93 * DO NOT use Jmsg in this routine.
95 * Note in this routine, we do not use Jmsg because it may be
96 * called to get attributes of a non-existent file, which is
97 * "normal" if a new file is found during Verify.
99 * The following is a bit of a kludge: because we always backup a
100 * directory entry, we can end up with two copies of the directory
101 * in the backup. One is when we encounter the directory and find
102 * we cannot recurse into it, and the other is when we find an
103 * explicit mention of the directory. This can also happen if the
104 * use includes the directory twice. In this case, Verify
105 * VolumeToCatalog fails because we have two copies in the catalog,
106 * and only the first one is marked (twice). So, when calling from Verify,
107 * jr is not NULL and we know jr->FileIndex is the fileindex
108 * of the version of the directory/file we actually want and do
109 * a more explicit SQL search.
112 int db_get_file_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr, FILE_DBR *fdbr)
116 char ed1[50], ed2[50], ed3[50];
118 if (jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG) {
120 "SELECT FileId, LStat, MD5 FROM File,Job WHERE "
121 "File.JobId=Job.JobId AND File.PathId=%s AND "
122 "File.FilenameId=%s AND Job.Type='B' AND Job.JobStatus IN ('T','W') AND "
123 "ClientId=%s ORDER BY StartTime DESC LIMIT 1",
124 edit_int64(fdbr->PathId, ed1),
125 edit_int64(fdbr->FilenameId, ed2),
126 edit_int64(jr->ClientId,ed3));
129 "SELECT FileId, LStat, MD5 FROM File WHERE File.JobId=%s AND File.PathId=%s AND "
130 "File.FilenameId=%s",
131 edit_int64(fdbr->JobId, ed1),
132 edit_int64(fdbr->PathId, ed2),
133 edit_int64(fdbr->FilenameId,ed3));
135 Dmsg3(450, "Get_file_record JobId=%u FilenameId=%u PathId=%u\n",
136 fdbr->JobId, fdbr->FilenameId, fdbr->PathId);
138 Dmsg1(100, "Query=%s\n", mdb->cmd);
140 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
141 mdb->num_rows = sql_num_rows(mdb);
142 Dmsg1(050, "get_file_record num_rows=%d\n", (int)mdb->num_rows);
143 if (mdb->num_rows >= 1) {
144 if ((row = sql_fetch_row(mdb)) == NULL) {
145 Mmsg1(mdb->errmsg, _("Error fetching row: %s\n"), sql_strerror(mdb));
147 fdbr->FileId = (FileId_t)str_to_int64(row[0]);
148 bstrncpy(fdbr->LStat, row[1], sizeof(fdbr->LStat));
149 bstrncpy(fdbr->Digest, row[2], sizeof(fdbr->Digest));
151 if (mdb->num_rows > 1) {
152 Mmsg3(mdb->errmsg, _("get_file_record want 1 got rows=%d PathId=%s FilenameId=%s\n"),
154 edit_int64(fdbr->PathId, ed1),
155 edit_int64(fdbr->FilenameId, ed2));
156 Dmsg1(000, "=== Problem! %s", mdb->errmsg);
160 Mmsg2(mdb->errmsg, _("File record for PathId=%s FilenameId=%s not found.\n"),
161 edit_int64(fdbr->PathId, ed1),
162 edit_int64(fdbr->FilenameId, ed2));
164 sql_free_result(mdb);
166 Mmsg(mdb->errmsg, _("File record not found in Catalog.\n"));
172 /* Get Filename record
173 * Returns: 0 on failure
174 * FilenameId on success
176 * DO NOT use Jmsg in this routine (see notes for get_file_record)
178 static int db_get_filename_record(JCR *jcr, B_DB *mdb)
183 mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*mdb->fnl+2);
184 db_escape_string(jcr, mdb, mdb->esc_name, mdb->fname, mdb->fnl);
186 Mmsg(mdb->cmd, "SELECT FilenameId FROM Filename WHERE Name='%s'", mdb->esc_name);
187 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
189 mdb->num_rows = sql_num_rows(mdb);
190 if (mdb->num_rows > 1) {
191 Mmsg2(mdb->errmsg, _("More than one Filename!: %s for file: %s\n"),
192 edit_uint64(mdb->num_rows, ed1), mdb->fname);
193 Jmsg(jcr, M_WARNING, 0, "%s", mdb->errmsg);
195 if (mdb->num_rows >= 1) {
196 if ((row = sql_fetch_row(mdb)) == NULL) {
197 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
199 FilenameId = str_to_int64(row[0]);
200 if (FilenameId <= 0) {
201 Mmsg2(mdb->errmsg, _("Get DB Filename record %s found bad record: %d\n"),
202 mdb->cmd, FilenameId);
207 Mmsg1(mdb->errmsg, _("Filename record: %s not found.\n"), mdb->fname);
209 sql_free_result(mdb);
211 Mmsg(mdb->errmsg, _("Filename record: %s not found in Catalog.\n"), mdb->fname);
217 * Returns: 0 on failure
220 * DO NOT use Jmsg in this routine (see notes for get_file_record)
222 int db_get_path_record(JCR *jcr, B_DB *mdb)
227 mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*mdb->pnl+2);
228 db_escape_string(jcr, mdb, mdb->esc_name, mdb->path, mdb->pnl);
230 if (mdb->cached_path_id != 0 && mdb->cached_path_len == mdb->pnl &&
231 strcmp(mdb->cached_path, mdb->path) == 0) {
232 return mdb->cached_path_id;
235 Mmsg(mdb->cmd, "SELECT PathId FROM Path WHERE Path='%s'", mdb->esc_name);
237 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
239 mdb->num_rows = sql_num_rows(mdb);
240 if (mdb->num_rows > 1) {
241 Mmsg2(mdb->errmsg, _("More than one Path!: %s for path: %s\n"),
242 edit_uint64(mdb->num_rows, ed1), mdb->path);
243 Jmsg(jcr, M_WARNING, 0, "%s", mdb->errmsg);
245 /* Even if there are multiple paths, take the first one */
246 if (mdb->num_rows >= 1) {
247 if ((row = sql_fetch_row(mdb)) == NULL) {
248 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
250 PathId = str_to_int64(row[0]);
252 Mmsg2(mdb->errmsg, _("Get DB path record %s found bad record: %s\n"),
253 mdb->cmd, edit_int64(PathId, ed1));
257 if (PathId != mdb->cached_path_id) {
258 mdb->cached_path_id = PathId;
259 mdb->cached_path_len = mdb->pnl;
260 pm_strcpy(mdb->cached_path, mdb->path);
265 Mmsg1(mdb->errmsg, _("Path record: %s not found.\n"), mdb->path);
267 sql_free_result(mdb);
269 Mmsg(mdb->errmsg, _("Path record: %s not found in Catalog.\n"), mdb->path);
276 * Get Job record for given JobId or Job name
277 * Returns: false on failure
280 bool db_get_job_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr)
286 if (jr->JobId == 0) {
287 Mmsg(mdb->cmd, "SELECT VolSessionId,VolSessionTime,"
288 "PoolId,StartTime,EndTime,JobFiles,JobBytes,JobTDate,Job,JobStatus,"
289 "Type,Level,ClientId,Name,PriorJobId,RealEndTime,JobId,FileSetId,"
290 "SchedTime,RealEndTime,ReadBytes,HasBase "
291 "FROM Job WHERE Job='%s'", jr->Job);
293 Mmsg(mdb->cmd, "SELECT VolSessionId,VolSessionTime,"
294 "PoolId,StartTime,EndTime,JobFiles,JobBytes,JobTDate,Job,JobStatus,"
295 "Type,Level,ClientId,Name,PriorJobId,RealEndTime,JobId,FileSetId,"
296 "SchedTime,RealEndTime,ReadBytes,HasBase "
297 "FROM Job WHERE JobId=%s",
298 edit_int64(jr->JobId, ed1));
301 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
303 return false; /* failed */
305 if ((row = sql_fetch_row(mdb)) == NULL) {
306 Mmsg1(mdb->errmsg, _("No Job found for JobId %s\n"), edit_int64(jr->JobId, ed1));
307 sql_free_result(mdb);
309 return false; /* failed */
312 jr->VolSessionId = str_to_uint64(row[0]);
313 jr->VolSessionTime = str_to_uint64(row[1]);
314 jr->PoolId = str_to_int64(row[2]);
315 bstrncpy(jr->cStartTime, row[3]!=NULL?row[3]:"", sizeof(jr->cStartTime));
316 bstrncpy(jr->cEndTime, row[4]!=NULL?row[4]:"", sizeof(jr->cEndTime));
317 jr->JobFiles = str_to_int64(row[5]);
318 jr->JobBytes = str_to_int64(row[6]);
319 jr->JobTDate = str_to_int64(row[7]);
320 bstrncpy(jr->Job, row[8]!=NULL?row[8]:"", sizeof(jr->Job));
321 jr->JobStatus = row[9]!=NULL?(int)*row[9]:JS_FatalError;
322 jr->JobType = row[10]!=NULL?(int)*row[10]:JT_BACKUP;
323 jr->JobLevel = row[11]!=NULL?(int)*row[11]:L_NONE;
324 jr->ClientId = str_to_uint64(row[12]!=NULL?row[12]:(char *)"");
325 bstrncpy(jr->Name, row[13]!=NULL?row[13]:"", sizeof(jr->Name));
326 jr->PriorJobId = str_to_uint64(row[14]!=NULL?row[14]:(char *)"");
327 bstrncpy(jr->cRealEndTime, row[15]!=NULL?row[15]:"", sizeof(jr->cRealEndTime));
328 if (jr->JobId == 0) {
329 jr->JobId = str_to_int64(row[16]);
331 jr->FileSetId = str_to_int64(row[17]);
332 bstrncpy(jr->cSchedTime, row[3]!=NULL?row[18]:"", sizeof(jr->cSchedTime));
333 bstrncpy(jr->cRealEndTime, row[3]!=NULL?row[19]:"", sizeof(jr->cRealEndTime));
334 jr->ReadBytes = str_to_int64(row[20]);
335 jr->StartTime = str_to_utime(jr->cStartTime);
336 jr->SchedTime = str_to_utime(jr->cSchedTime);
337 jr->EndTime = str_to_utime(jr->cEndTime);
338 jr->RealEndTime = str_to_utime(jr->cRealEndTime);
339 jr->HasBase = str_to_int64(row[21]);
340 sql_free_result(mdb);
347 * Find VolumeNames for a given JobId
348 * Returns: 0 on error or no Volumes found
349 * number of volumes on success
350 * Volumes are concatenated in VolumeNames
351 * separated by a vertical bar (|) in the order
352 * that they were written.
354 * Returns: number of volumes on success
356 int db_get_job_volume_names(JCR *jcr, B_DB *mdb, JobId_t JobId, POOLMEM **VolumeNames)
364 /* Get one entry per VolumeName, but "sort" by VolIndex */
366 "SELECT VolumeName,MAX(VolIndex) FROM JobMedia,Media WHERE "
367 "JobMedia.JobId=%s AND JobMedia.MediaId=Media.MediaId "
368 "GROUP BY VolumeName "
369 "ORDER BY 2 ASC", edit_int64(JobId,ed1));
371 Dmsg1(130, "VolNam=%s\n", mdb->cmd);
373 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
374 mdb->num_rows = sql_num_rows(mdb);
375 Dmsg1(130, "Num rows=%d\n", mdb->num_rows);
376 if (mdb->num_rows <= 0) {
377 Mmsg1(mdb->errmsg, _("No volumes found for JobId=%d\n"), JobId);
380 stat = mdb->num_rows;
381 for (i=0; i < stat; i++) {
382 if ((row = sql_fetch_row(mdb)) == NULL) {
383 Mmsg2(mdb->errmsg, _("Error fetching row %d: ERR=%s\n"), i, sql_strerror(mdb));
384 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
388 if (*VolumeNames[0] != 0) {
389 pm_strcat(VolumeNames, "|");
391 pm_strcat(VolumeNames, row[0]);
395 sql_free_result(mdb);
397 Mmsg(mdb->errmsg, _("No Volume for JobId %d found in Catalog.\n"), JobId);
404 * Find Volume parameters for a give JobId
405 * Returns: 0 on error or no Volumes found
406 * number of volumes on success
407 * List of Volumes and start/end file/blocks (malloced structure!)
409 * Returns: number of volumes on success
411 int db_get_job_volume_parameters(JCR *jcr, B_DB *mdb, JobId_t JobId, VOL_PARAMS **VolParams)
417 VOL_PARAMS *Vols = NULL;
421 "SELECT VolumeName,MediaType,FirstIndex,LastIndex,StartFile,"
422 "JobMedia.EndFile,StartBlock,JobMedia.EndBlock,"
423 "Slot,StorageId,InChanger"
424 " FROM JobMedia,Media WHERE JobMedia.JobId=%s"
425 " AND JobMedia.MediaId=Media.MediaId ORDER BY VolIndex,JobMediaId",
426 edit_int64(JobId, ed1));
428 Dmsg1(130, "VolNam=%s\n", mdb->cmd);
429 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
430 mdb->num_rows = sql_num_rows(mdb);
431 Dmsg1(200, "Num rows=%d\n", mdb->num_rows);
432 if (mdb->num_rows <= 0) {
433 Mmsg1(mdb->errmsg, _("No volumes found for JobId=%d\n"), JobId);
436 stat = mdb->num_rows;
439 *VolParams = Vols = (VOL_PARAMS *)malloc(stat * sizeof(VOL_PARAMS));
440 SId = (DBId_t *)malloc(stat * sizeof(DBId_t));
442 for (i=0; i < stat; i++) {
443 if ((row = sql_fetch_row(mdb)) == NULL) {
444 Mmsg2(mdb->errmsg, _("Error fetching row %d: ERR=%s\n"), i, sql_strerror(mdb));
445 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
450 uint32_t StartBlock, EndBlock, StartFile, EndFile;
451 bstrncpy(Vols[i].VolumeName, row[0], MAX_NAME_LENGTH);
452 bstrncpy(Vols[i].MediaType, row[1], MAX_NAME_LENGTH);
453 Vols[i].FirstIndex = str_to_uint64(row[2]);
454 Vols[i].LastIndex = str_to_uint64(row[3]);
455 StartFile = str_to_uint64(row[4]);
456 EndFile = str_to_uint64(row[5]);
457 StartBlock = str_to_uint64(row[6]);
458 EndBlock = str_to_uint64(row[7]);
459 Vols[i].StartAddr = (((uint64_t)StartFile)<<32) | StartBlock;
460 Vols[i].EndAddr = (((uint64_t)EndFile)<<32) | EndBlock;
461 Vols[i].Slot = str_to_uint64(row[8]);
462 StorageId = str_to_uint64(row[9]);
463 Vols[i].InChanger = str_to_uint64(row[10]);
464 Vols[i].Storage[0] = 0;
468 for (i=0; i < stat; i++) {
470 Mmsg(mdb->cmd, "SELECT Name from Storage WHERE StorageId=%s",
471 edit_int64(SId[i], ed1));
472 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
473 if ((row = sql_fetch_row(mdb)) && row[0]) {
474 bstrncpy(Vols[i].Storage, row[0], MAX_NAME_LENGTH);
483 sql_free_result(mdb);
492 * Get the number of pool records
494 * Returns: -1 on failure
497 int db_get_num_pool_records(JCR *jcr, B_DB *mdb)
502 Mmsg(mdb->cmd, "SELECT count(*) from Pool");
503 stat = get_sql_record_max(jcr, mdb);
509 * This function returns a list of all the Pool record ids.
510 * The caller must free ids if non-NULL.
512 * Returns 0: on failure
515 int db_get_pool_ids(JCR *jcr, B_DB *mdb, int *num_ids, uint32_t *ids[])
524 Mmsg(mdb->cmd, "SELECT PoolId FROM Pool");
525 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
526 *num_ids = sql_num_rows(mdb);
528 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
529 while ((row = sql_fetch_row(mdb)) != NULL) {
530 id[i++] = str_to_uint64(row[0]);
534 sql_free_result(mdb);
537 Mmsg(mdb->errmsg, _("Pool id select failed: ERR=%s\n"), sql_strerror(mdb));
538 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
546 * This function returns a list of all the Client record ids.
547 * The caller must free ids if non-NULL.
549 * Returns 0: on failure
552 int db_get_client_ids(JCR *jcr, B_DB *mdb, int *num_ids, uint32_t *ids[])
561 Mmsg(mdb->cmd, "SELECT ClientId FROM Client ORDER BY Name");
562 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
563 *num_ids = sql_num_rows(mdb);
565 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
566 while ((row = sql_fetch_row(mdb)) != NULL) {
567 id[i++] = str_to_uint64(row[0]);
571 sql_free_result(mdb);
574 Mmsg(mdb->errmsg, _("Client id select failed: ERR=%s\n"), sql_strerror(mdb));
575 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
585 * If the PoolId is non-zero, we get its record,
586 * otherwise, we search on the PoolName
588 * Returns: false on failure
591 bool db_get_pool_record(JCR *jcr, B_DB *mdb, POOL_DBR *pdbr)
598 if (pdbr->PoolId != 0) { /* find by id */
600 "SELECT PoolId,Name,NumVols,MaxVols,UseOnce,UseCatalog,AcceptAnyVolume,"
601 "AutoPrune,Recycle,VolRetention,VolUseDuration,MaxVolJobs,MaxVolFiles,"
602 "MaxVolBytes,PoolType,LabelType,LabelFormat,RecyclePoolId,ScratchPoolId,"
603 "ActionOnPurge FROM Pool WHERE Pool.PoolId=%s",
604 edit_int64(pdbr->PoolId, ed1));
605 } else { /* find by name */
607 "SELECT PoolId,Name,NumVols,MaxVols,UseOnce,UseCatalog,AcceptAnyVolume,"
608 "AutoPrune,Recycle,VolRetention,VolUseDuration,MaxVolJobs,MaxVolFiles,"
609 "MaxVolBytes,PoolType,LabelType,LabelFormat,RecyclePoolId,ScratchPoolId,"
610 "ActionOnPurge FROM Pool WHERE Pool.Name='%s'",
613 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
614 mdb->num_rows = sql_num_rows(mdb);
615 if (mdb->num_rows > 1) {
617 Mmsg1(mdb->errmsg, _("More than one Pool!: %s\n"),
618 edit_uint64(mdb->num_rows, ed1));
619 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
620 } else if (mdb->num_rows == 1) {
621 if ((row = sql_fetch_row(mdb)) == NULL) {
622 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
623 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
625 pdbr->PoolId = str_to_int64(row[0]);
626 bstrncpy(pdbr->Name, row[1]!=NULL?row[1]:"", sizeof(pdbr->Name));
627 pdbr->NumVols = str_to_int64(row[2]);
628 pdbr->MaxVols = str_to_int64(row[3]);
629 pdbr->UseOnce = str_to_int64(row[4]);
630 pdbr->UseCatalog = str_to_int64(row[5]);
631 pdbr->AcceptAnyVolume = str_to_int64(row[6]);
632 pdbr->AutoPrune = str_to_int64(row[7]);
633 pdbr->Recycle = str_to_int64(row[8]);
634 pdbr->VolRetention = str_to_int64(row[9]);
635 pdbr->VolUseDuration = str_to_int64(row[10]);
636 pdbr->MaxVolJobs = str_to_int64(row[11]);
637 pdbr->MaxVolFiles = str_to_int64(row[12]);
638 pdbr->MaxVolBytes = str_to_uint64(row[13]);
639 bstrncpy(pdbr->PoolType, row[14]!=NULL?row[14]:"", sizeof(pdbr->PoolType));
640 pdbr->LabelType = str_to_int64(row[15]);
641 bstrncpy(pdbr->LabelFormat, row[16]!=NULL?row[16]:"", sizeof(pdbr->LabelFormat));
642 pdbr->RecyclePoolId = str_to_int64(row[17]);
643 pdbr->ScratchPoolId = str_to_int64(row[18]);
644 pdbr->ActionOnPurge = str_to_int32(row[19]);
648 sql_free_result(mdb);
652 Mmsg(mdb->cmd, "SELECT count(*) from Media WHERE PoolId=%s",
653 edit_int64(pdbr->PoolId, ed1));
654 NumVols = get_sql_record_max(jcr, mdb);
655 Dmsg2(400, "Actual NumVols=%d Pool NumVols=%d\n", NumVols, pdbr->NumVols);
656 if (NumVols != pdbr->NumVols) {
657 pdbr->NumVols = NumVols;
658 db_update_pool_record(jcr, mdb, pdbr);
661 Mmsg(mdb->errmsg, _("Pool record not found in Catalog.\n"));
668 * If the ClientId is non-zero, we get its record,
669 * otherwise, we search on the Client Name
671 * Returns: 0 on failure
674 int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr)
681 if (cdbr->ClientId != 0) { /* find by id */
683 "SELECT ClientId,Name,Uname,AutoPrune,FileRetention,JobRetention "
684 "FROM Client WHERE Client.ClientId=%s",
685 edit_int64(cdbr->ClientId, ed1));
686 } else { /* find by name */
688 "SELECT ClientId,Name,Uname,AutoPrune,FileRetention,JobRetention "
689 "FROM Client WHERE Client.Name='%s'", cdbr->Name);
692 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
693 mdb->num_rows = sql_num_rows(mdb);
694 if (mdb->num_rows > 1) {
695 Mmsg1(mdb->errmsg, _("More than one Client!: %s\n"),
696 edit_uint64(mdb->num_rows, ed1));
697 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
698 } else if (mdb->num_rows == 1) {
699 if ((row = sql_fetch_row(mdb)) == NULL) {
700 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
701 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
703 cdbr->ClientId = str_to_int64(row[0]);
704 bstrncpy(cdbr->Name, row[1]!=NULL?row[1]:"", sizeof(cdbr->Name));
705 bstrncpy(cdbr->Uname, row[2]!=NULL?row[2]:"", sizeof(cdbr->Uname));
706 cdbr->AutoPrune = str_to_int64(row[3]);
707 cdbr->FileRetention = str_to_int64(row[4]);
708 cdbr->JobRetention = str_to_int64(row[5]);
712 Mmsg(mdb->errmsg, _("Client record not found in Catalog.\n"));
714 sql_free_result(mdb);
716 Mmsg(mdb->errmsg, _("Client record not found in Catalog.\n"));
725 * Returns: 0 on failure
728 int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr)
733 Mmsg(mdb->cmd, "SELECT \"MinValue\",\"MaxValue\",CurrentValue,WrapCounter "
734 "FROM Counters WHERE Counter='%s'", cr->Counter);
736 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
737 mdb->num_rows = sql_num_rows(mdb);
739 /* If more than one, report error, but return first row */
740 if (mdb->num_rows > 1) {
741 Mmsg1(mdb->errmsg, _("More than one Counter!: %d\n"), (int)(mdb->num_rows));
742 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
744 if (mdb->num_rows >= 1) {
745 if ((row = sql_fetch_row(mdb)) == NULL) {
746 Mmsg1(mdb->errmsg, _("error fetching Counter row: %s\n"), sql_strerror(mdb));
747 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
748 sql_free_result(mdb);
752 cr->MinValue = str_to_int64(row[0]);
753 cr->MaxValue = str_to_int64(row[1]);
754 cr->CurrentValue = str_to_int64(row[2]);
756 bstrncpy(cr->WrapCounter, row[3], sizeof(cr->WrapCounter));
758 cr->WrapCounter[0] = 0;
760 sql_free_result(mdb);
764 sql_free_result(mdb);
766 Mmsg(mdb->errmsg, _("Counter record: %s not found in Catalog.\n"), cr->Counter);
773 /* Get FileSet Record
774 * If the FileSetId is non-zero, we get its record,
775 * otherwise, we search on the name
777 * Returns: 0 on failure
780 int db_get_fileset_record(JCR *jcr, B_DB *mdb, FILESET_DBR *fsr)
787 if (fsr->FileSetId != 0) { /* find by id */
789 "SELECT FileSetId,FileSet,MD5,CreateTime FROM FileSet "
790 "WHERE FileSetId=%s",
791 edit_int64(fsr->FileSetId, ed1));
792 } else { /* find by name */
794 "SELECT FileSetId,FileSet,MD5,CreateTime FROM FileSet "
795 "WHERE FileSet='%s' ORDER BY CreateTime DESC LIMIT 1", fsr->FileSet);
798 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
799 mdb->num_rows = sql_num_rows(mdb);
800 if (mdb->num_rows > 1) {
802 Mmsg1(mdb->errmsg, _("Error got %s FileSets but expected only one!\n"),
803 edit_uint64(mdb->num_rows, ed1));
804 sql_data_seek(mdb, mdb->num_rows-1);
806 if ((row = sql_fetch_row(mdb)) == NULL) {
807 Mmsg1(mdb->errmsg, _("FileSet record \"%s\" not found.\n"), fsr->FileSet);
809 fsr->FileSetId = str_to_int64(row[0]);
810 bstrncpy(fsr->FileSet, row[1]!=NULL?row[1]:"", sizeof(fsr->FileSet));
811 bstrncpy(fsr->MD5, row[2]!=NULL?row[2]:"", sizeof(fsr->MD5));
812 bstrncpy(fsr->cCreateTime, row[3]!=NULL?row[3]:"", sizeof(fsr->cCreateTime));
813 stat = fsr->FileSetId;
815 sql_free_result(mdb);
817 Mmsg(mdb->errmsg, _("FileSet record not found in Catalog.\n"));
825 * Get the number of Media records
827 * Returns: -1 on failure
830 int db_get_num_media_records(JCR *jcr, B_DB *mdb)
835 Mmsg(mdb->cmd, "SELECT count(*) from Media");
836 stat = get_sql_record_max(jcr, mdb);
842 * This function returns a list of all the Media record ids for
843 * the current Pool, the correct Media Type, Recyle, Enabled, StorageId, VolBytes
844 * VolumeName if specified
845 * The caller must free ids if non-NULL.
847 * Returns false: on failure
850 bool db_get_media_ids(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr, int *num_ids, uint32_t *ids[])
857 char buf[MAX_NAME_LENGTH*3]; /* Can contain MAX_NAME_LENGTH*2+1 + AND ....='' */
858 char esc[MAX_NAME_LENGTH*2+1];
863 Mmsg(mdb->cmd, "SELECT DISTINCT MediaId FROM Media WHERE Recycle=%d AND Enabled=%d ",
864 mr->Recycle, mr->Enabled);
866 if (*mr->MediaType) {
867 db_escape_string(jcr, mdb, esc, mr->MediaType, strlen(mr->MediaType));
868 bsnprintf(buf, sizeof(buf), "AND MediaType='%s' ", esc);
869 pm_strcat(mdb->cmd, buf);
873 bsnprintf(buf, sizeof(buf), "AND StorageId=%s ", edit_uint64(mr->StorageId, ed1));
874 pm_strcat(mdb->cmd, buf);
878 bsnprintf(buf, sizeof(buf), "AND PoolId=%s ", edit_uint64(mr->PoolId, ed1));
879 pm_strcat(mdb->cmd, buf);
883 bsnprintf(buf, sizeof(buf), "AND VolBytes > %s ", edit_uint64(mr->VolBytes, ed1));
884 pm_strcat(mdb->cmd, buf);
887 if (*mr->VolumeName) {
888 db_escape_string(jcr, mdb, esc, mr->VolumeName, strlen(mr->VolumeName));
889 bsnprintf(buf, sizeof(buf), "AND VolumeName = '%s' ", esc);
890 pm_strcat(mdb->cmd, buf);
893 if (*mr->VolStatus) {
894 db_escape_string(jcr, mdb, esc, mr->VolStatus, strlen(mr->VolStatus));
895 bsnprintf(buf, sizeof(buf), "AND VolStatus = '%s' ", esc);
896 pm_strcat(mdb->cmd, buf);
899 Dmsg1(100, "q=%s\n", mdb->cmd);
901 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
902 *num_ids = sql_num_rows(mdb);
904 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
905 while ((row = sql_fetch_row(mdb)) != NULL) {
906 id[i++] = str_to_uint64(row[0]);
910 sql_free_result(mdb);
913 Mmsg(mdb->errmsg, _("Media id select failed: ERR=%s\n"), sql_strerror(mdb));
914 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
923 * This function returns a list of all the DBIds that are returned
926 * Returns false: on failure
929 bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids)
937 if (QUERY_DB(jcr, mdb, query.c_str())) {
938 ids.num_ids = sql_num_rows(mdb);
939 if (ids.num_ids > 0) {
940 if (ids.max_ids < ids.num_ids) {
942 ids.DBId = (DBId_t *)malloc(ids.num_ids * sizeof(DBId_t));
944 while ((row = sql_fetch_row(mdb)) != NULL) {
945 ids.DBId[i++] = str_to_uint64(row[0]);
948 sql_free_result(mdb);
951 Mmsg(mdb->errmsg, _("query dbids failed: ERR=%s\n"), sql_strerror(mdb));
952 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
961 * Returns: false: on failure
964 bool db_get_media_record(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr)
971 if (mr->MediaId == 0 && mr->VolumeName[0] == 0) {
972 Mmsg(mdb->cmd, "SELECT count(*) from Media");
973 mr->MediaId = get_sql_record_max(jcr, mdb);
977 if (mr->MediaId != 0) { /* find by id */
978 Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks,"
979 "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes,"
980 "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs,"
981 "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger,"
982 "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId,"
983 "Enabled,LocationId,RecycleCount,InitialWrite,"
984 "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime,ActionOnPurge "
985 "FROM Media WHERE MediaId=%s",
986 edit_int64(mr->MediaId, ed1));
987 } else { /* find by name */
988 Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks,"
989 "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes,"
990 "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs,"
991 "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger,"
992 "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId,"
993 "Enabled,LocationId,RecycleCount,InitialWrite,"
994 "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime,ActionOnPurge "
995 "FROM Media WHERE VolumeName='%s'", mr->VolumeName);
998 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
1000 mdb->num_rows = sql_num_rows(mdb);
1001 if (mdb->num_rows > 1) {
1002 Mmsg1(mdb->errmsg, _("More than one Volume!: %s\n"),
1003 edit_uint64(mdb->num_rows, ed1));
1004 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
1005 } else if (mdb->num_rows == 1) {
1006 if ((row = sql_fetch_row(mdb)) == NULL) {
1007 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
1008 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
1011 mr->MediaId = str_to_int64(row[0]);
1012 bstrncpy(mr->VolumeName, row[1]!=NULL?row[1]:"", sizeof(mr->VolumeName));
1013 mr->VolJobs = str_to_int64(row[2]);
1014 mr->VolFiles = str_to_int64(row[3]);
1015 mr->VolBlocks = str_to_int64(row[4]);
1016 mr->VolBytes = str_to_uint64(row[5]);
1017 mr->VolMounts = str_to_int64(row[6]);
1018 mr->VolErrors = str_to_int64(row[7]);
1019 mr->VolWrites = str_to_int64(row[8]);
1020 mr->MaxVolBytes = str_to_uint64(row[9]);
1021 mr->VolCapacityBytes = str_to_uint64(row[10]);
1022 bstrncpy(mr->MediaType, row[11]!=NULL?row[11]:"", sizeof(mr->MediaType));
1023 bstrncpy(mr->VolStatus, row[12]!=NULL?row[12]:"", sizeof(mr->VolStatus));
1024 mr->PoolId = str_to_int64(row[13]);
1025 mr->VolRetention = str_to_uint64(row[14]);
1026 mr->VolUseDuration = str_to_uint64(row[15]);
1027 mr->MaxVolJobs = str_to_int64(row[16]);
1028 mr->MaxVolFiles = str_to_int64(row[17]);
1029 mr->Recycle = str_to_int64(row[18]);
1030 mr->Slot = str_to_int64(row[19]);
1031 bstrncpy(mr->cFirstWritten, row[20]!=NULL?row[20]:"", sizeof(mr->cFirstWritten));
1032 mr->FirstWritten = (time_t)str_to_utime(mr->cFirstWritten);
1033 bstrncpy(mr->cLastWritten, row[21]!=NULL?row[21]:"", sizeof(mr->cLastWritten));
1034 mr->LastWritten = (time_t)str_to_utime(mr->cLastWritten);
1035 mr->InChanger = str_to_uint64(row[22]);
1036 mr->EndFile = str_to_uint64(row[23]);
1037 mr->EndBlock = str_to_uint64(row[24]);
1038 mr->VolParts = str_to_int64(row[25]);
1039 mr->LabelType = str_to_int64(row[26]);
1040 bstrncpy(mr->cLabelDate, row[27]!=NULL?row[27]:"", sizeof(mr->cLabelDate));
1041 mr->LabelDate = (time_t)str_to_utime(mr->cLabelDate);
1042 mr->StorageId = str_to_int64(row[28]);
1043 mr->Enabled = str_to_int64(row[29]);
1044 mr->LocationId = str_to_int64(row[30]);
1045 mr->RecycleCount = str_to_int64(row[31]);
1046 bstrncpy(mr->cInitialWrite, row[32]!=NULL?row[32]:"", sizeof(mr->cInitialWrite));
1047 mr->InitialWrite = (time_t)str_to_utime(mr->cInitialWrite);
1048 mr->ScratchPoolId = str_to_int64(row[33]);
1049 mr->RecyclePoolId = str_to_int64(row[34]);
1050 mr->VolReadTime = str_to_int64(row[35]);
1051 mr->VolWriteTime = str_to_int64(row[36]);
1052 mr->ActionOnPurge = str_to_int32(row[37]);
1057 if (mr->MediaId != 0) {
1058 Mmsg1(mdb->errmsg, _("Media record MediaId=%s not found.\n"),
1059 edit_int64(mr->MediaId, ed1));
1061 Mmsg1(mdb->errmsg, _("Media record for Volume \"%s\" not found.\n"),
1065 sql_free_result(mdb);
1067 if (mr->MediaId != 0) {
1068 Mmsg(mdb->errmsg, _("Media record for MediaId=%u not found in Catalog.\n"),
1071 Mmsg(mdb->errmsg, _("Media record for Vol=%s not found in Catalog.\n"),
1079 * Find the last "accurate" backup state (that can take deleted files in
1081 * 1) Get all files with jobid in list (F subquery)
1082 * Get all files in BaseFiles with jobid in list
1083 * 2) Take only the last version of each file (Temp subquery) => accurate list
1085 * 3) Join the result to file table to get fileindex, jobid and lstat information
1087 * TODO: See if we can do the SORT only if needed (as an argument)
1089 bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids,
1090 DB_RESULT_HANDLER *result_handler, void *ctx)
1094 Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
1098 POOL_MEM buf(PM_MESSAGE);
1100 #define new_db_get_file_list
1101 #ifdef new_db_get_file_list
1102 POOL_MEM buf2(PM_MESSAGE);
1103 Mmsg(buf2, select_recent_version_with_basejob[db_type],
1104 jobids, jobids, jobids, jobids);
1106 "SELECT Path.Path, Filename.Name, Temp.FileIndex, Temp.JobId, LStat, MD5 "
1107 "FROM ( %s ) AS Temp "
1108 "JOIN Filename ON (Filename.FilenameId = Temp.FilenameId) "
1109 "JOIN Path ON (Path.PathId = Temp.PathId) "
1110 "WHERE FileIndex > 0 "
1111 "ORDER BY Temp.JobId, FileIndex ASC",/* Return sorted by JobId, */
1112 /* FileIndex for restore code */
1116 * I am not sure that this works the same as the code in ua_restore.c but it
1117 * is very similar. The accurate-test fails in a restore. Bad file count.
1119 Mmsg(buf, uar_sel_files, jobids);
1122 return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
1126 * This procedure gets the base jobid list used by jobids,
1128 bool db_get_used_base_jobids(JCR *jcr, B_DB *mdb,
1129 POOLMEM *jobids, db_list_ctx *result)
1133 "SELECT DISTINCT BaseJobId "
1134 " FROM Job JOIN BaseFiles USING (JobId) "
1135 " WHERE Job.HasBase = 1 "
1136 " AND Job.JobId IN (%s) ", jobids);
1137 return db_sql_query(mdb, buf.c_str(), db_list_handler, result);
1140 /* The decision do change an incr/diff was done before
1142 * Differential : get the last full id
1143 * Incremental : get the last full + last diff + last incr(s) ids
1145 * If you specify jr->StartTime, it will be used to limit the search
1146 * in the time. (usually now)
1148 * TODO: look and merge from ua_restore.c
1150 bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb,
1151 JOB_DBR *jr, db_list_ctx *jobids)
1154 char clientid[50], jobid[50], filesetid[50];
1155 char date[MAX_TIME_LENGTH];
1156 POOL_MEM query(PM_FNAME);
1158 /* Take the current time as upper limit if nothing else specified */
1159 utime_t StartTime = (jr->StartTime)?jr->StartTime:time(NULL);
1161 bstrutime(date, sizeof(date), StartTime + 1);
1164 /* First, find the last good Full backup for this job/client/fileset */
1166 "CREATE TABLE btemp3%s AS "
1167 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1168 "FROM Job JOIN FileSet USING (FileSetId) "
1169 "WHERE ClientId = %s "
1170 "AND Level='F' AND JobStatus IN ('T','W') AND Type='B' "
1171 "AND StartTime<'%s' "
1172 "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1173 "ORDER BY Job.JobTDate DESC LIMIT 1",
1174 edit_uint64(jcr->JobId, jobid),
1175 edit_uint64(jr->ClientId, clientid),
1177 edit_uint64(jr->FileSetId, filesetid));
1179 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1183 if (jr->JobLevel == L_INCREMENTAL || jr->JobLevel == L_VIRTUAL_FULL) {
1184 /* Now, find the last differential backup after the last full */
1186 "INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
1187 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1188 "FROM Job JOIN FileSet USING (FileSetId) "
1189 "WHERE ClientId = %s "
1190 "AND Level='D' AND JobStatus IN ('T','W') AND Type='B' "
1191 "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
1192 "AND StartTime < '%s' "
1193 "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1194 "ORDER BY Job.JobTDate DESC LIMIT 1 ",
1201 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1205 /* We just have to take all incremental after the last Full/Diff */
1207 "INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
1208 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1209 "FROM Job JOIN FileSet USING (FileSetId) "
1210 "WHERE ClientId = %s "
1211 "AND Level='I' AND JobStatus IN ('T','W') AND Type='B' "
1212 "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
1213 "AND StartTime < '%s' "
1214 "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1215 "ORDER BY Job.JobTDate DESC ",
1221 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1226 /* build a jobid list ie: 1,2,3,4 */
1227 Mmsg(query, "SELECT JobId FROM btemp3%s ORDER by JobTDate", jobid);
1228 db_sql_query(mdb, query.c_str(), db_list_handler, jobids);
1229 Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids->list);
1233 Mmsg(query, "DROP TABLE btemp3%s", jobid);
1234 db_sql_query(mdb, query.c_str(), NULL, NULL);
1239 bool db_get_base_file_list(JCR *jcr, B_DB *mdb,
1240 DB_RESULT_HANDLER *result_handler, void *ctx)
1242 POOL_MEM buf(PM_MESSAGE);
1245 "SELECT Path, Name, FileIndex, JobId, LStat, MD5 "
1246 "FROM new_basefile%lld ORDER BY JobId, FileIndex ASC",
1247 (uint64_t) jcr->JobId);
1249 return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
1252 bool db_get_base_jobid(JCR *jcr, B_DB *mdb, JOB_DBR *jr, JobId_t *jobid)
1254 POOL_MEM query(PM_FNAME);
1257 char date[MAX_TIME_LENGTH];
1259 // char clientid[50], filesetid[50];
1264 StartTime = (jr->StartTime)?jr->StartTime:time(NULL);
1265 bstrutime(date, sizeof(date), StartTime + 1);
1267 /* we can take also client name, fileset, etc... */
1270 "SELECT JobId, Job, StartTime, EndTime, JobTDate, PurgedFiles "
1272 // "JOIN FileSet USING (FileSetId) JOIN Client USING (ClientId) "
1273 "WHERE Job.Name = '%s' "
1274 "AND Level='B' AND JobStatus IN ('T','W') AND Type='B' "
1275 // "AND FileSet.FileSet= '%s' "
1276 // "AND Client.Name = '%s' "
1277 "AND StartTime<'%s' "
1278 "ORDER BY Job.JobTDate DESC LIMIT 1",
1280 // edit_uint64(jr->ClientId, clientid),
1281 // edit_uint64(jr->FileSetId, filesetid));
1284 Dmsg1(10, "db_get_base_jobid q=%s\n", query.c_str());
1285 if (!db_sql_query(mdb, query.c_str(), db_int64_handler, &lctx)) {
1288 *jobid = (JobId_t) lctx.value;
1290 Dmsg1(10, "db_get_base_jobid=%lld\n", *jobid);
1297 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI */