2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2010 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
40 * The following is necessary so that we do not include
41 * the dummy external definition of DB.
43 #define __SQL_C /* indicate that this is sql.c */
48 #if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI
50 /* -----------------------------------------------------------------------
52 * Generic Routines (or almost generic)
54 * -----------------------------------------------------------------------
57 /* Forward referenced functions */
58 static int db_get_file_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr, FILE_DBR *fdbr);
59 static int db_get_filename_record(JCR *jcr, B_DB *mdb);
63 * Given a full filename (with path), look up the File record
64 * (with attributes) in the database.
66 * Returns: 0 on failure
67 * 1 on success with the File record in FILE_DBR
69 int db_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JOB_DBR *jr, FILE_DBR *fdbr)
72 Dmsg1(100, "db_get_file_att_record fname=%s \n", fname);
75 split_path_and_file(jcr, mdb, fname);
77 fdbr->FilenameId = db_get_filename_record(jcr, mdb);
79 fdbr->PathId = db_get_path_record(jcr, mdb);
81 stat = db_get_file_record(jcr, mdb, jr, fdbr);
91 * Returns: 0 on failure
94 * DO NOT use Jmsg in this routine.
96 * Note in this routine, we do not use Jmsg because it may be
97 * called to get attributes of a non-existent file, which is
98 * "normal" if a new file is found during Verify.
100 * The following is a bit of a kludge: because we always backup a
101 * directory entry, we can end up with two copies of the directory
102 * in the backup. One is when we encounter the directory and find
103 * we cannot recurse into it, and the other is when we find an
104 * explicit mention of the directory. This can also happen if the
105 * use includes the directory twice. In this case, Verify
106 * VolumeToCatalog fails because we have two copies in the catalog,
107 * and only the first one is marked (twice). So, when calling from Verify,
108 * jr is not NULL and we know jr->FileIndex is the fileindex
109 * of the version of the directory/file we actually want and do
110 * a more explicit SQL search.
113 int db_get_file_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr, FILE_DBR *fdbr)
117 char ed1[50], ed2[50], ed3[50];
119 if (jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG) {
121 "SELECT FileId, LStat, MD5 FROM File,Job WHERE "
122 "File.JobId=Job.JobId AND File.PathId=%s AND "
123 "File.FilenameId=%s AND Job.Type='B' AND Job.JobStatus IN ('T','W') AND "
124 "ClientId=%s ORDER BY StartTime DESC LIMIT 1",
125 edit_int64(fdbr->PathId, ed1),
126 edit_int64(fdbr->FilenameId, ed2),
127 edit_int64(jr->ClientId,ed3));
130 "SELECT FileId, LStat, MD5 FROM File WHERE File.JobId=%s AND File.PathId=%s AND "
131 "File.FilenameId=%s",
132 edit_int64(fdbr->JobId, ed1),
133 edit_int64(fdbr->PathId, ed2),
134 edit_int64(fdbr->FilenameId,ed3));
136 Dmsg3(450, "Get_file_record JobId=%u FilenameId=%u PathId=%u\n",
137 fdbr->JobId, fdbr->FilenameId, fdbr->PathId);
139 Dmsg1(100, "Query=%s\n", mdb->cmd);
141 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
142 mdb->num_rows = sql_num_rows(mdb);
143 Dmsg1(050, "get_file_record num_rows=%d\n", (int)mdb->num_rows);
144 if (mdb->num_rows >= 1) {
145 if ((row = sql_fetch_row(mdb)) == NULL) {
146 Mmsg1(mdb->errmsg, _("Error fetching row: %s\n"), sql_strerror(mdb));
148 fdbr->FileId = (FileId_t)str_to_int64(row[0]);
149 bstrncpy(fdbr->LStat, row[1], sizeof(fdbr->LStat));
150 bstrncpy(fdbr->Digest, row[2], sizeof(fdbr->Digest));
152 if (mdb->num_rows > 1) {
153 Mmsg3(mdb->errmsg, _("get_file_record want 1 got rows=%d PathId=%s FilenameId=%s\n"),
155 edit_int64(fdbr->PathId, ed1),
156 edit_int64(fdbr->FilenameId, ed2));
157 Dmsg1(000, "=== Problem! %s", mdb->errmsg);
161 Mmsg2(mdb->errmsg, _("File record for PathId=%s FilenameId=%s not found.\n"),
162 edit_int64(fdbr->PathId, ed1),
163 edit_int64(fdbr->FilenameId, ed2));
165 sql_free_result(mdb);
167 Mmsg(mdb->errmsg, _("File record not found in Catalog.\n"));
174 * Get Filename record
175 * Returns: 0 on failure
176 * FilenameId on success
178 * DO NOT use Jmsg in this routine (see notes for get_file_record)
180 static int db_get_filename_record(JCR *jcr, B_DB *mdb)
185 mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*mdb->fnl+2);
186 db_escape_string(jcr, mdb, mdb->esc_name, mdb->fname, mdb->fnl);
188 Mmsg(mdb->cmd, "SELECT FilenameId FROM Filename WHERE Name='%s'", mdb->esc_name);
189 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
191 mdb->num_rows = sql_num_rows(mdb);
192 if (mdb->num_rows > 1) {
193 Mmsg2(mdb->errmsg, _("More than one Filename!: %s for file: %s\n"),
194 edit_uint64(mdb->num_rows, ed1), mdb->fname);
195 Jmsg(jcr, M_WARNING, 0, "%s", mdb->errmsg);
197 if (mdb->num_rows >= 1) {
198 if ((row = sql_fetch_row(mdb)) == NULL) {
199 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
201 FilenameId = str_to_int64(row[0]);
202 if (FilenameId <= 0) {
203 Mmsg2(mdb->errmsg, _("Get DB Filename record %s found bad record: %d\n"),
204 mdb->cmd, FilenameId);
209 Mmsg1(mdb->errmsg, _("Filename record: %s not found.\n"), mdb->fname);
211 sql_free_result(mdb);
213 Mmsg(mdb->errmsg, _("Filename record: %s not found in Catalog.\n"), mdb->fname);
220 * Returns: 0 on failure
223 * DO NOT use Jmsg in this routine (see notes for get_file_record)
225 int db_get_path_record(JCR *jcr, B_DB *mdb)
230 mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*mdb->pnl+2);
231 db_escape_string(jcr, mdb, mdb->esc_name, mdb->path, mdb->pnl);
233 if (mdb->cached_path_id != 0 && mdb->cached_path_len == mdb->pnl &&
234 strcmp(mdb->cached_path, mdb->path) == 0) {
235 return mdb->cached_path_id;
238 Mmsg(mdb->cmd, "SELECT PathId FROM Path WHERE Path='%s'", mdb->esc_name);
240 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
242 mdb->num_rows = sql_num_rows(mdb);
243 if (mdb->num_rows > 1) {
244 Mmsg2(mdb->errmsg, _("More than one Path!: %s for path: %s\n"),
245 edit_uint64(mdb->num_rows, ed1), mdb->path);
246 Jmsg(jcr, M_WARNING, 0, "%s", mdb->errmsg);
248 /* Even if there are multiple paths, take the first one */
249 if (mdb->num_rows >= 1) {
250 if ((row = sql_fetch_row(mdb)) == NULL) {
251 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
253 PathId = str_to_int64(row[0]);
255 Mmsg2(mdb->errmsg, _("Get DB path record %s found bad record: %s\n"),
256 mdb->cmd, edit_int64(PathId, ed1));
260 if (PathId != mdb->cached_path_id) {
261 mdb->cached_path_id = PathId;
262 mdb->cached_path_len = mdb->pnl;
263 pm_strcpy(mdb->cached_path, mdb->path);
268 Mmsg1(mdb->errmsg, _("Path record: %s not found.\n"), mdb->path);
270 sql_free_result(mdb);
272 Mmsg(mdb->errmsg, _("Path record: %s not found in Catalog.\n"), mdb->path);
279 * Get Job record for given JobId or Job name
280 * Returns: false on failure
283 bool db_get_job_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr)
289 if (jr->JobId == 0) {
290 Mmsg(mdb->cmd, "SELECT VolSessionId,VolSessionTime,"
291 "PoolId,StartTime,EndTime,JobFiles,JobBytes,JobTDate,Job,JobStatus,"
292 "Type,Level,ClientId,Name,PriorJobId,RealEndTime,JobId,FileSetId,"
293 "SchedTime,RealEndTime,ReadBytes,HasBase,PurgedFiles "
294 "FROM Job WHERE Job='%s'", jr->Job);
296 Mmsg(mdb->cmd, "SELECT VolSessionId,VolSessionTime,"
297 "PoolId,StartTime,EndTime,JobFiles,JobBytes,JobTDate,Job,JobStatus,"
298 "Type,Level,ClientId,Name,PriorJobId,RealEndTime,JobId,FileSetId,"
299 "SchedTime,RealEndTime,ReadBytes,HasBase,PurgedFiles "
300 "FROM Job WHERE JobId=%s",
301 edit_int64(jr->JobId, ed1));
304 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
306 return false; /* failed */
308 if ((row = sql_fetch_row(mdb)) == NULL) {
309 Mmsg1(mdb->errmsg, _("No Job found for JobId %s\n"), edit_int64(jr->JobId, ed1));
310 sql_free_result(mdb);
312 return false; /* failed */
315 jr->VolSessionId = str_to_uint64(row[0]);
316 jr->VolSessionTime = str_to_uint64(row[1]);
317 jr->PoolId = str_to_int64(row[2]);
318 bstrncpy(jr->cStartTime, row[3]!=NULL?row[3]:"", sizeof(jr->cStartTime));
319 bstrncpy(jr->cEndTime, row[4]!=NULL?row[4]:"", sizeof(jr->cEndTime));
320 jr->JobFiles = str_to_int64(row[5]);
321 jr->JobBytes = str_to_int64(row[6]);
322 jr->JobTDate = str_to_int64(row[7]);
323 bstrncpy(jr->Job, row[8]!=NULL?row[8]:"", sizeof(jr->Job));
324 jr->JobStatus = row[9]!=NULL?(int)*row[9]:JS_FatalError;
325 jr->JobType = row[10]!=NULL?(int)*row[10]:JT_BACKUP;
326 jr->JobLevel = row[11]!=NULL?(int)*row[11]:L_NONE;
327 jr->ClientId = str_to_uint64(row[12]!=NULL?row[12]:(char *)"");
328 bstrncpy(jr->Name, row[13]!=NULL?row[13]:"", sizeof(jr->Name));
329 jr->PriorJobId = str_to_uint64(row[14]!=NULL?row[14]:(char *)"");
330 bstrncpy(jr->cRealEndTime, row[15]!=NULL?row[15]:"", sizeof(jr->cRealEndTime));
331 if (jr->JobId == 0) {
332 jr->JobId = str_to_int64(row[16]);
334 jr->FileSetId = str_to_int64(row[17]);
335 bstrncpy(jr->cSchedTime, row[3]!=NULL?row[18]:"", sizeof(jr->cSchedTime));
336 bstrncpy(jr->cRealEndTime, row[3]!=NULL?row[19]:"", sizeof(jr->cRealEndTime));
337 jr->ReadBytes = str_to_int64(row[20]);
338 jr->StartTime = str_to_utime(jr->cStartTime);
339 jr->SchedTime = str_to_utime(jr->cSchedTime);
340 jr->EndTime = str_to_utime(jr->cEndTime);
341 jr->RealEndTime = str_to_utime(jr->cRealEndTime);
342 jr->HasBase = str_to_int64(row[21]);
343 jr->PurgedFiles = str_to_int64(row[22]);
344 sql_free_result(mdb);
351 * Find VolumeNames for a given JobId
352 * Returns: 0 on error or no Volumes found
353 * number of volumes on success
354 * Volumes are concatenated in VolumeNames
355 * separated by a vertical bar (|) in the order
356 * that they were written.
358 * Returns: number of volumes on success
360 int db_get_job_volume_names(JCR *jcr, B_DB *mdb, JobId_t JobId, POOLMEM **VolumeNames)
368 /* Get one entry per VolumeName, but "sort" by VolIndex */
370 "SELECT VolumeName,MAX(VolIndex) FROM JobMedia,Media WHERE "
371 "JobMedia.JobId=%s AND JobMedia.MediaId=Media.MediaId "
372 "GROUP BY VolumeName "
373 "ORDER BY 2 ASC", edit_int64(JobId,ed1));
375 Dmsg1(130, "VolNam=%s\n", mdb->cmd);
377 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
378 mdb->num_rows = sql_num_rows(mdb);
379 Dmsg1(130, "Num rows=%d\n", mdb->num_rows);
380 if (mdb->num_rows <= 0) {
381 Mmsg1(mdb->errmsg, _("No volumes found for JobId=%d\n"), JobId);
384 stat = mdb->num_rows;
385 for (i=0; i < stat; i++) {
386 if ((row = sql_fetch_row(mdb)) == NULL) {
387 Mmsg2(mdb->errmsg, _("Error fetching row %d: ERR=%s\n"), i, sql_strerror(mdb));
388 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
392 if (*VolumeNames[0] != 0) {
393 pm_strcat(VolumeNames, "|");
395 pm_strcat(VolumeNames, row[0]);
399 sql_free_result(mdb);
401 Mmsg(mdb->errmsg, _("No Volume for JobId %d found in Catalog.\n"), JobId);
408 * Find Volume parameters for a give JobId
409 * Returns: 0 on error or no Volumes found
410 * number of volumes on success
411 * List of Volumes and start/end file/blocks (malloced structure!)
413 * Returns: number of volumes on success
415 int db_get_job_volume_parameters(JCR *jcr, B_DB *mdb, JobId_t JobId, VOL_PARAMS **VolParams)
421 VOL_PARAMS *Vols = NULL;
425 "SELECT VolumeName,MediaType,FirstIndex,LastIndex,StartFile,"
426 "JobMedia.EndFile,StartBlock,JobMedia.EndBlock,"
427 "Slot,StorageId,InChanger"
428 " FROM JobMedia,Media WHERE JobMedia.JobId=%s"
429 " AND JobMedia.MediaId=Media.MediaId ORDER BY VolIndex,JobMediaId",
430 edit_int64(JobId, ed1));
432 Dmsg1(130, "VolNam=%s\n", mdb->cmd);
433 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
434 mdb->num_rows = sql_num_rows(mdb);
435 Dmsg1(200, "Num rows=%d\n", mdb->num_rows);
436 if (mdb->num_rows <= 0) {
437 Mmsg1(mdb->errmsg, _("No volumes found for JobId=%d\n"), JobId);
440 stat = mdb->num_rows;
443 *VolParams = Vols = (VOL_PARAMS *)malloc(stat * sizeof(VOL_PARAMS));
444 SId = (DBId_t *)malloc(stat * sizeof(DBId_t));
446 for (i=0; i < stat; i++) {
447 if ((row = sql_fetch_row(mdb)) == NULL) {
448 Mmsg2(mdb->errmsg, _("Error fetching row %d: ERR=%s\n"), i, sql_strerror(mdb));
449 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
454 uint32_t StartBlock, EndBlock, StartFile, EndFile;
455 bstrncpy(Vols[i].VolumeName, row[0], MAX_NAME_LENGTH);
456 bstrncpy(Vols[i].MediaType, row[1], MAX_NAME_LENGTH);
457 Vols[i].FirstIndex = str_to_uint64(row[2]);
458 Vols[i].LastIndex = str_to_uint64(row[3]);
459 StartFile = str_to_uint64(row[4]);
460 EndFile = str_to_uint64(row[5]);
461 StartBlock = str_to_uint64(row[6]);
462 EndBlock = str_to_uint64(row[7]);
463 Vols[i].StartAddr = (((uint64_t)StartFile)<<32) | StartBlock;
464 Vols[i].EndAddr = (((uint64_t)EndFile)<<32) | EndBlock;
465 Vols[i].Slot = str_to_uint64(row[8]);
466 StorageId = str_to_uint64(row[9]);
467 Vols[i].InChanger = str_to_uint64(row[10]);
468 Vols[i].Storage[0] = 0;
472 for (i=0; i < stat; i++) {
474 Mmsg(mdb->cmd, "SELECT Name from Storage WHERE StorageId=%s",
475 edit_int64(SId[i], ed1));
476 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
477 if ((row = sql_fetch_row(mdb)) && row[0]) {
478 bstrncpy(Vols[i].Storage, row[0], MAX_NAME_LENGTH);
487 sql_free_result(mdb);
496 * Get the number of pool records
498 * Returns: -1 on failure
501 int db_get_num_pool_records(JCR *jcr, B_DB *mdb)
506 Mmsg(mdb->cmd, "SELECT count(*) from Pool");
507 stat = get_sql_record_max(jcr, mdb);
513 * This function returns a list of all the Pool record ids.
514 * The caller must free ids if non-NULL.
516 * Returns 0: on failure
519 int db_get_pool_ids(JCR *jcr, B_DB *mdb, int *num_ids, uint32_t *ids[])
528 Mmsg(mdb->cmd, "SELECT PoolId FROM Pool");
529 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
530 *num_ids = sql_num_rows(mdb);
532 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
533 while ((row = sql_fetch_row(mdb)) != NULL) {
534 id[i++] = str_to_uint64(row[0]);
538 sql_free_result(mdb);
541 Mmsg(mdb->errmsg, _("Pool id select failed: ERR=%s\n"), sql_strerror(mdb));
542 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
550 * This function returns a list of all the Client record ids.
551 * The caller must free ids if non-NULL.
553 * Returns 0: on failure
556 int db_get_client_ids(JCR *jcr, B_DB *mdb, int *num_ids, uint32_t *ids[])
565 Mmsg(mdb->cmd, "SELECT ClientId FROM Client ORDER BY Name");
566 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
567 *num_ids = sql_num_rows(mdb);
569 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
570 while ((row = sql_fetch_row(mdb)) != NULL) {
571 id[i++] = str_to_uint64(row[0]);
575 sql_free_result(mdb);
578 Mmsg(mdb->errmsg, _("Client id select failed: ERR=%s\n"), sql_strerror(mdb));
579 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
590 * If the PoolId is non-zero, we get its record,
591 * otherwise, we search on the PoolName
593 * Returns: false on failure
596 bool db_get_pool_record(JCR *jcr, B_DB *mdb, POOL_DBR *pdbr)
603 if (pdbr->PoolId != 0) { /* find by id */
605 "SELECT PoolId,Name,NumVols,MaxVols,UseOnce,UseCatalog,AcceptAnyVolume,"
606 "AutoPrune,Recycle,VolRetention,VolUseDuration,MaxVolJobs,MaxVolFiles,"
607 "MaxVolBytes,PoolType,LabelType,LabelFormat,RecyclePoolId,ScratchPoolId,"
608 "ActionOnPurge FROM Pool WHERE Pool.PoolId=%s",
609 edit_int64(pdbr->PoolId, ed1));
610 } else { /* find by name */
612 "SELECT PoolId,Name,NumVols,MaxVols,UseOnce,UseCatalog,AcceptAnyVolume,"
613 "AutoPrune,Recycle,VolRetention,VolUseDuration,MaxVolJobs,MaxVolFiles,"
614 "MaxVolBytes,PoolType,LabelType,LabelFormat,RecyclePoolId,ScratchPoolId,"
615 "ActionOnPurge FROM Pool WHERE Pool.Name='%s'",
618 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
619 mdb->num_rows = sql_num_rows(mdb);
620 if (mdb->num_rows > 1) {
622 Mmsg1(mdb->errmsg, _("More than one Pool!: %s\n"),
623 edit_uint64(mdb->num_rows, ed1));
624 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
625 } else if (mdb->num_rows == 1) {
626 if ((row = sql_fetch_row(mdb)) == NULL) {
627 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
628 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
630 pdbr->PoolId = str_to_int64(row[0]);
631 bstrncpy(pdbr->Name, row[1]!=NULL?row[1]:"", sizeof(pdbr->Name));
632 pdbr->NumVols = str_to_int64(row[2]);
633 pdbr->MaxVols = str_to_int64(row[3]);
634 pdbr->UseOnce = str_to_int64(row[4]);
635 pdbr->UseCatalog = str_to_int64(row[5]);
636 pdbr->AcceptAnyVolume = str_to_int64(row[6]);
637 pdbr->AutoPrune = str_to_int64(row[7]);
638 pdbr->Recycle = str_to_int64(row[8]);
639 pdbr->VolRetention = str_to_int64(row[9]);
640 pdbr->VolUseDuration = str_to_int64(row[10]);
641 pdbr->MaxVolJobs = str_to_int64(row[11]);
642 pdbr->MaxVolFiles = str_to_int64(row[12]);
643 pdbr->MaxVolBytes = str_to_uint64(row[13]);
644 bstrncpy(pdbr->PoolType, row[14]!=NULL?row[14]:"", sizeof(pdbr->PoolType));
645 pdbr->LabelType = str_to_int64(row[15]);
646 bstrncpy(pdbr->LabelFormat, row[16]!=NULL?row[16]:"", sizeof(pdbr->LabelFormat));
647 pdbr->RecyclePoolId = str_to_int64(row[17]);
648 pdbr->ScratchPoolId = str_to_int64(row[18]);
649 pdbr->ActionOnPurge = str_to_int32(row[19]);
653 sql_free_result(mdb);
657 Mmsg(mdb->cmd, "SELECT count(*) from Media WHERE PoolId=%s",
658 edit_int64(pdbr->PoolId, ed1));
659 NumVols = get_sql_record_max(jcr, mdb);
660 Dmsg2(400, "Actual NumVols=%d Pool NumVols=%d\n", NumVols, pdbr->NumVols);
661 if (NumVols != pdbr->NumVols) {
662 pdbr->NumVols = NumVols;
663 db_update_pool_record(jcr, mdb, pdbr);
666 Mmsg(mdb->errmsg, _("Pool record not found in Catalog.\n"));
674 * If the ClientId is non-zero, we get its record,
675 * otherwise, we search on the Client Name
677 * Returns: 0 on failure
680 int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr)
687 if (cdbr->ClientId != 0) { /* find by id */
689 "SELECT ClientId,Name,Uname,AutoPrune,FileRetention,JobRetention "
690 "FROM Client WHERE Client.ClientId=%s",
691 edit_int64(cdbr->ClientId, ed1));
692 } else { /* find by name */
694 "SELECT ClientId,Name,Uname,AutoPrune,FileRetention,JobRetention "
695 "FROM Client WHERE Client.Name='%s'", cdbr->Name);
698 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
699 mdb->num_rows = sql_num_rows(mdb);
700 if (mdb->num_rows > 1) {
701 Mmsg1(mdb->errmsg, _("More than one Client!: %s\n"),
702 edit_uint64(mdb->num_rows, ed1));
703 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
704 } else if (mdb->num_rows == 1) {
705 if ((row = sql_fetch_row(mdb)) == NULL) {
706 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
707 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
709 cdbr->ClientId = str_to_int64(row[0]);
710 bstrncpy(cdbr->Name, row[1]!=NULL?row[1]:"", sizeof(cdbr->Name));
711 bstrncpy(cdbr->Uname, row[2]!=NULL?row[2]:"", sizeof(cdbr->Uname));
712 cdbr->AutoPrune = str_to_int64(row[3]);
713 cdbr->FileRetention = str_to_int64(row[4]);
714 cdbr->JobRetention = str_to_int64(row[5]);
718 Mmsg(mdb->errmsg, _("Client record not found in Catalog.\n"));
720 sql_free_result(mdb);
722 Mmsg(mdb->errmsg, _("Client record not found in Catalog.\n"));
731 * Returns: 0 on failure
734 int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr)
739 Mmsg(mdb->cmd, "SELECT \"MinValue\",\"MaxValue\",CurrentValue,WrapCounter "
740 "FROM Counters WHERE Counter='%s'", cr->Counter);
742 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
743 mdb->num_rows = sql_num_rows(mdb);
745 /* If more than one, report error, but return first row */
746 if (mdb->num_rows > 1) {
747 Mmsg1(mdb->errmsg, _("More than one Counter!: %d\n"), (int)(mdb->num_rows));
748 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
750 if (mdb->num_rows >= 1) {
751 if ((row = sql_fetch_row(mdb)) == NULL) {
752 Mmsg1(mdb->errmsg, _("error fetching Counter row: %s\n"), sql_strerror(mdb));
753 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
754 sql_free_result(mdb);
758 cr->MinValue = str_to_int64(row[0]);
759 cr->MaxValue = str_to_int64(row[1]);
760 cr->CurrentValue = str_to_int64(row[2]);
762 bstrncpy(cr->WrapCounter, row[3], sizeof(cr->WrapCounter));
764 cr->WrapCounter[0] = 0;
766 sql_free_result(mdb);
770 sql_free_result(mdb);
772 Mmsg(mdb->errmsg, _("Counter record: %s not found in Catalog.\n"), cr->Counter);
781 * If the FileSetId is non-zero, we get its record,
782 * otherwise, we search on the name
784 * Returns: 0 on failure
787 int db_get_fileset_record(JCR *jcr, B_DB *mdb, FILESET_DBR *fsr)
794 if (fsr->FileSetId != 0) { /* find by id */
796 "SELECT FileSetId,FileSet,MD5,CreateTime FROM FileSet "
797 "WHERE FileSetId=%s",
798 edit_int64(fsr->FileSetId, ed1));
799 } else { /* find by name */
801 "SELECT FileSetId,FileSet,MD5,CreateTime FROM FileSet "
802 "WHERE FileSet='%s' ORDER BY CreateTime DESC LIMIT 1", fsr->FileSet);
805 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
806 mdb->num_rows = sql_num_rows(mdb);
807 if (mdb->num_rows > 1) {
809 Mmsg1(mdb->errmsg, _("Error got %s FileSets but expected only one!\n"),
810 edit_uint64(mdb->num_rows, ed1));
811 sql_data_seek(mdb, mdb->num_rows-1);
813 if ((row = sql_fetch_row(mdb)) == NULL) {
814 Mmsg1(mdb->errmsg, _("FileSet record \"%s\" not found.\n"), fsr->FileSet);
816 fsr->FileSetId = str_to_int64(row[0]);
817 bstrncpy(fsr->FileSet, row[1]!=NULL?row[1]:"", sizeof(fsr->FileSet));
818 bstrncpy(fsr->MD5, row[2]!=NULL?row[2]:"", sizeof(fsr->MD5));
819 bstrncpy(fsr->cCreateTime, row[3]!=NULL?row[3]:"", sizeof(fsr->cCreateTime));
820 stat = fsr->FileSetId;
822 sql_free_result(mdb);
824 Mmsg(mdb->errmsg, _("FileSet record not found in Catalog.\n"));
832 * Get the number of Media records
834 * Returns: -1 on failure
837 int db_get_num_media_records(JCR *jcr, B_DB *mdb)
842 Mmsg(mdb->cmd, "SELECT count(*) from Media");
843 stat = get_sql_record_max(jcr, mdb);
849 * This function returns a list of all the Media record ids for
850 * the current Pool, the correct Media Type, Recyle, Enabled, StorageId, VolBytes
851 * VolumeName if specified
852 * The caller must free ids if non-NULL.
854 * Returns false: on failure
857 bool db_get_media_ids(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr, int *num_ids, uint32_t *ids[])
864 char buf[MAX_NAME_LENGTH*3]; /* Can contain MAX_NAME_LENGTH*2+1 + AND ....='' */
865 char esc[MAX_NAME_LENGTH*2+1];
870 Mmsg(mdb->cmd, "SELECT DISTINCT MediaId FROM Media WHERE Recycle=%d AND Enabled=%d ",
871 mr->Recycle, mr->Enabled);
873 if (*mr->MediaType) {
874 db_escape_string(jcr, mdb, esc, mr->MediaType, strlen(mr->MediaType));
875 bsnprintf(buf, sizeof(buf), "AND MediaType='%s' ", esc);
876 pm_strcat(mdb->cmd, buf);
880 bsnprintf(buf, sizeof(buf), "AND StorageId=%s ", edit_uint64(mr->StorageId, ed1));
881 pm_strcat(mdb->cmd, buf);
885 bsnprintf(buf, sizeof(buf), "AND PoolId=%s ", edit_uint64(mr->PoolId, ed1));
886 pm_strcat(mdb->cmd, buf);
890 bsnprintf(buf, sizeof(buf), "AND VolBytes > %s ", edit_uint64(mr->VolBytes, ed1));
891 pm_strcat(mdb->cmd, buf);
894 if (*mr->VolumeName) {
895 db_escape_string(jcr, mdb, esc, mr->VolumeName, strlen(mr->VolumeName));
896 bsnprintf(buf, sizeof(buf), "AND VolumeName = '%s' ", esc);
897 pm_strcat(mdb->cmd, buf);
900 if (*mr->VolStatus) {
901 db_escape_string(jcr, mdb, esc, mr->VolStatus, strlen(mr->VolStatus));
902 bsnprintf(buf, sizeof(buf), "AND VolStatus = '%s' ", esc);
903 pm_strcat(mdb->cmd, buf);
906 Dmsg1(100, "q=%s\n", mdb->cmd);
908 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
909 *num_ids = sql_num_rows(mdb);
911 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
912 while ((row = sql_fetch_row(mdb)) != NULL) {
913 id[i++] = str_to_uint64(row[0]);
917 sql_free_result(mdb);
920 Mmsg(mdb->errmsg, _("Media id select failed: ERR=%s\n"), sql_strerror(mdb));
921 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
930 * This function returns a list of all the DBIds that are returned
933 * Returns false: on failure
936 bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids)
944 if (QUERY_DB(jcr, mdb, query.c_str())) {
945 ids.num_ids = sql_num_rows(mdb);
946 if (ids.num_ids > 0) {
947 if (ids.max_ids < ids.num_ids) {
949 ids.DBId = (DBId_t *)malloc(ids.num_ids * sizeof(DBId_t));
951 while ((row = sql_fetch_row(mdb)) != NULL) {
952 ids.DBId[i++] = str_to_uint64(row[0]);
955 sql_free_result(mdb);
958 Mmsg(mdb->errmsg, _("query dbids failed: ERR=%s\n"), sql_strerror(mdb));
959 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
969 * Returns: false: on failure
972 bool db_get_media_record(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr)
979 if (mr->MediaId == 0 && mr->VolumeName[0] == 0) {
980 Mmsg(mdb->cmd, "SELECT count(*) from Media");
981 mr->MediaId = get_sql_record_max(jcr, mdb);
985 if (mr->MediaId != 0) { /* find by id */
986 Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks,"
987 "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes,"
988 "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs,"
989 "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger,"
990 "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId,"
991 "Enabled,LocationId,RecycleCount,InitialWrite,"
992 "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime,ActionOnPurge "
993 "FROM Media WHERE MediaId=%s",
994 edit_int64(mr->MediaId, ed1));
995 } else { /* find by name */
996 Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks,"
997 "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes,"
998 "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs,"
999 "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger,"
1000 "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId,"
1001 "Enabled,LocationId,RecycleCount,InitialWrite,"
1002 "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime,ActionOnPurge "
1003 "FROM Media WHERE VolumeName='%s'", mr->VolumeName);
1006 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
1008 mdb->num_rows = sql_num_rows(mdb);
1009 if (mdb->num_rows > 1) {
1010 Mmsg1(mdb->errmsg, _("More than one Volume!: %s\n"),
1011 edit_uint64(mdb->num_rows, ed1));
1012 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
1013 } else if (mdb->num_rows == 1) {
1014 if ((row = sql_fetch_row(mdb)) == NULL) {
1015 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
1016 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
1019 mr->MediaId = str_to_int64(row[0]);
1020 bstrncpy(mr->VolumeName, row[1]!=NULL?row[1]:"", sizeof(mr->VolumeName));
1021 mr->VolJobs = str_to_int64(row[2]);
1022 mr->VolFiles = str_to_int64(row[3]);
1023 mr->VolBlocks = str_to_int64(row[4]);
1024 mr->VolBytes = str_to_uint64(row[5]);
1025 mr->VolMounts = str_to_int64(row[6]);
1026 mr->VolErrors = str_to_int64(row[7]);
1027 mr->VolWrites = str_to_int64(row[8]);
1028 mr->MaxVolBytes = str_to_uint64(row[9]);
1029 mr->VolCapacityBytes = str_to_uint64(row[10]);
1030 bstrncpy(mr->MediaType, row[11]!=NULL?row[11]:"", sizeof(mr->MediaType));
1031 bstrncpy(mr->VolStatus, row[12]!=NULL?row[12]:"", sizeof(mr->VolStatus));
1032 mr->PoolId = str_to_int64(row[13]);
1033 mr->VolRetention = str_to_uint64(row[14]);
1034 mr->VolUseDuration = str_to_uint64(row[15]);
1035 mr->MaxVolJobs = str_to_int64(row[16]);
1036 mr->MaxVolFiles = str_to_int64(row[17]);
1037 mr->Recycle = str_to_int64(row[18]);
1038 mr->Slot = str_to_int64(row[19]);
1039 bstrncpy(mr->cFirstWritten, row[20]!=NULL?row[20]:"", sizeof(mr->cFirstWritten));
1040 mr->FirstWritten = (time_t)str_to_utime(mr->cFirstWritten);
1041 bstrncpy(mr->cLastWritten, row[21]!=NULL?row[21]:"", sizeof(mr->cLastWritten));
1042 mr->LastWritten = (time_t)str_to_utime(mr->cLastWritten);
1043 mr->InChanger = str_to_uint64(row[22]);
1044 mr->EndFile = str_to_uint64(row[23]);
1045 mr->EndBlock = str_to_uint64(row[24]);
1046 mr->VolParts = str_to_int64(row[25]);
1047 mr->LabelType = str_to_int64(row[26]);
1048 bstrncpy(mr->cLabelDate, row[27]!=NULL?row[27]:"", sizeof(mr->cLabelDate));
1049 mr->LabelDate = (time_t)str_to_utime(mr->cLabelDate);
1050 mr->StorageId = str_to_int64(row[28]);
1051 mr->Enabled = str_to_int64(row[29]);
1052 mr->LocationId = str_to_int64(row[30]);
1053 mr->RecycleCount = str_to_int64(row[31]);
1054 bstrncpy(mr->cInitialWrite, row[32]!=NULL?row[32]:"", sizeof(mr->cInitialWrite));
1055 mr->InitialWrite = (time_t)str_to_utime(mr->cInitialWrite);
1056 mr->ScratchPoolId = str_to_int64(row[33]);
1057 mr->RecyclePoolId = str_to_int64(row[34]);
1058 mr->VolReadTime = str_to_int64(row[35]);
1059 mr->VolWriteTime = str_to_int64(row[36]);
1060 mr->ActionOnPurge = str_to_int32(row[37]);
1065 if (mr->MediaId != 0) {
1066 Mmsg1(mdb->errmsg, _("Media record MediaId=%s not found.\n"),
1067 edit_int64(mr->MediaId, ed1));
1069 Mmsg1(mdb->errmsg, _("Media record for Volume \"%s\" not found.\n"),
1073 sql_free_result(mdb);
1075 if (mr->MediaId != 0) {
1076 Mmsg(mdb->errmsg, _("Media record for MediaId=%u not found in Catalog.\n"),
1079 Mmsg(mdb->errmsg, _("Media record for Vol=%s not found in Catalog.\n"),
1086 /* Remove all MD5 from a query (can save lot of memory with many files) */
1087 static void replace_md5(char *q)
1090 while ((p = strstr(p, ", MD5"))) {
1091 memset(p, ' ', 5 * sizeof(char));
1096 * Find the last "accurate" backup state (that can take deleted files in
1098 * 1) Get all files with jobid in list (F subquery)
1099 * Get all files in BaseFiles with jobid in list
1100 * 2) Take only the last version of each file (Temp subquery) => accurate list
1102 * 3) Join the result to file table to get fileindex, jobid and lstat information
1104 * TODO: See if we can do the SORT only if needed (as an argument)
1106 bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, bool use_md5,
1107 DB_RESULT_HANDLER *result_handler, void *ctx)
1111 Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
1115 POOL_MEM buf(PM_MESSAGE);
1117 #define new_db_get_file_list
1118 #ifdef new_db_get_file_list
1119 POOL_MEM buf2(PM_MESSAGE);
1120 Mmsg(buf2, select_recent_version_with_basejob[db_type],
1121 jobids, jobids, jobids, jobids);
1123 "SELECT Path.Path, Filename.Name, T1.FileIndex, T1.JobId, LStat, MarkId, MD5 "
1124 "FROM ( %s ) AS T1 "
1125 "JOIN Filename ON (Filename.FilenameId = T1.FilenameId) "
1126 "JOIN Path ON (Path.PathId = T1.PathId) "
1127 "WHERE FileIndex > 0 "
1128 "ORDER BY T1.JobTDate, FileIndex ASC",/* Return sorted by JobId, */
1129 /* FileIndex for restore code */
1132 replace_md5(buf.c_str());
1134 Dmsg1(100, "q=%s\n", buf.c_str());
1137 * I am not sure that this works the same as the code in ua_restore.c but it
1138 * is very similar. The accurate-test fails in a restore. Bad file count.
1140 Mmsg(buf, uar_sel_files, jobids);
1143 return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
1147 * This procedure gets the base jobid list used by jobids,
1149 bool db_get_used_base_jobids(JCR *jcr, B_DB *mdb,
1150 POOLMEM *jobids, db_list_ctx *result)
1154 "SELECT DISTINCT BaseJobId "
1155 " FROM Job JOIN BaseFiles USING (JobId) "
1156 " WHERE Job.HasBase = 1 "
1157 " AND Job.JobId IN (%s) ", jobids);
1158 return db_sql_query(mdb, buf.c_str(), db_list_handler, result);
1162 * The decision do change an incr/diff was done before
1164 * Differential : get the last full id
1165 * Incremental : get the last full + last diff + last incr(s) ids
1167 * If you specify jr->StartTime, it will be used to limit the search
1168 * in the time. (usually now)
1170 * TODO: look and merge from ua_restore.c
1172 bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb,
1173 JOB_DBR *jr, db_list_ctx *jobids)
1176 char clientid[50], jobid[50], filesetid[50];
1177 char date[MAX_TIME_LENGTH];
1178 POOL_MEM query(PM_FNAME);
1180 /* Take the current time as upper limit if nothing else specified */
1181 utime_t StartTime = (jr->StartTime)?jr->StartTime:time(NULL);
1183 bstrutime(date, sizeof(date), StartTime + 1);
1186 /* First, find the last good Full backup for this job/client/fileset */
1187 Mmsg(query, create_temp_accurate_jobids[db_type],
1188 edit_uint64(jcr->JobId, jobid),
1189 edit_uint64(jr->ClientId, clientid),
1191 edit_uint64(jr->FileSetId, filesetid));
1193 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1197 if (jr->JobLevel == L_INCREMENTAL || jr->JobLevel == L_VIRTUAL_FULL) {
1198 /* Now, find the last differential backup after the last full */
1200 "INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
1201 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1202 "FROM Job JOIN FileSet USING (FileSetId) "
1203 "WHERE ClientId = %s "
1204 "AND Level='D' AND JobStatus IN ('T','W') AND Type='B' "
1205 "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
1206 "AND StartTime < '%s' "
1207 "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1208 "ORDER BY Job.JobTDate DESC LIMIT 1 ",
1215 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1219 /* We just have to take all incremental after the last Full/Diff */
1221 "INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
1222 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1223 "FROM Job JOIN FileSet USING (FileSetId) "
1224 "WHERE ClientId = %s "
1225 "AND Level='I' AND JobStatus IN ('T','W') AND Type='B' "
1226 "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
1227 "AND StartTime < '%s' "
1228 "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1229 "ORDER BY Job.JobTDate DESC ",
1235 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1240 /* build a jobid list ie: 1,2,3,4 */
1241 Mmsg(query, "SELECT JobId FROM btemp3%s ORDER by JobTDate", jobid);
1242 db_sql_query(mdb, query.c_str(), db_list_handler, jobids);
1243 Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids->list);
1247 Mmsg(query, "DROP TABLE btemp3%s", jobid);
1248 db_sql_query(mdb, query.c_str(), NULL, NULL);
1253 bool db_get_base_file_list(JCR *jcr, B_DB *mdb, bool use_md5,
1254 DB_RESULT_HANDLER *result_handler, void *ctx)
1256 POOL_MEM buf(PM_MESSAGE);
1259 "SELECT Path, Name, FileIndex, JobId, LStat, 0 As MarkId, MD5 "
1260 "FROM new_basefile%lld ORDER BY JobId, FileIndex ASC",
1261 (uint64_t) jcr->JobId);
1264 replace_md5(buf.c_str());
1266 return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
1269 bool db_get_base_jobid(JCR *jcr, B_DB *mdb, JOB_DBR *jr, JobId_t *jobid)
1271 POOL_MEM query(PM_FNAME);
1274 char date[MAX_TIME_LENGTH];
1276 // char clientid[50], filesetid[50];
1281 StartTime = (jr->StartTime)?jr->StartTime:time(NULL);
1282 bstrutime(date, sizeof(date), StartTime + 1);
1284 /* we can take also client name, fileset, etc... */
1287 "SELECT JobId, Job, StartTime, EndTime, JobTDate, PurgedFiles "
1289 // "JOIN FileSet USING (FileSetId) JOIN Client USING (ClientId) "
1290 "WHERE Job.Name = '%s' "
1291 "AND Level='B' AND JobStatus IN ('T','W') AND Type='B' "
1292 // "AND FileSet.FileSet= '%s' "
1293 // "AND Client.Name = '%s' "
1294 "AND StartTime<'%s' "
1295 "ORDER BY Job.JobTDate DESC LIMIT 1",
1297 // edit_uint64(jr->ClientId, clientid),
1298 // edit_uint64(jr->FileSetId, filesetid));
1301 Dmsg1(10, "db_get_base_jobid q=%s\n", query.c_str());
1302 if (!db_sql_query(mdb, query.c_str(), db_int64_handler, &lctx)) {
1305 *jobid = (JobId_t) lctx.value;
1307 Dmsg1(10, "db_get_base_jobid=%lld\n", *jobid);
1314 /* Get JobIds associated with a volume */
1315 bool db_get_volume_jobids(JCR *jcr, B_DB *mdb,
1316 MEDIA_DBR *mr, db_list_ctx *lst)
1322 Mmsg(mdb->cmd, "SELECT DISTINCT JobId FROM JobMedia WHERE MediaId=%s",
1323 edit_int64(mr->MediaId, ed1));
1324 ret = db_sql_query(mdb, mdb->cmd, db_list_handler, lst);
1329 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI */