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 two of the GNU 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 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 "
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 "
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 sql_free_result(mdb);
350 * Find VolumeNames for a given JobId
351 * Returns: 0 on error or no Volumes found
352 * number of volumes on success
353 * Volumes are concatenated in VolumeNames
354 * separated by a vertical bar (|) in the order
355 * that they were written.
357 * Returns: number of volumes on success
359 int db_get_job_volume_names(JCR *jcr, B_DB *mdb, JobId_t JobId, POOLMEM **VolumeNames)
367 /* Get one entry per VolumeName, but "sort" by VolIndex */
369 "SELECT VolumeName,MAX(VolIndex) FROM JobMedia,Media WHERE "
370 "JobMedia.JobId=%s AND JobMedia.MediaId=Media.MediaId "
371 "GROUP BY VolumeName "
372 "ORDER BY 2 ASC", edit_int64(JobId,ed1));
374 Dmsg1(130, "VolNam=%s\n", mdb->cmd);
376 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
377 mdb->num_rows = sql_num_rows(mdb);
378 Dmsg1(130, "Num rows=%d\n", mdb->num_rows);
379 if (mdb->num_rows <= 0) {
380 Mmsg1(mdb->errmsg, _("No volumes found for JobId=%d\n"), JobId);
383 stat = mdb->num_rows;
384 for (i=0; i < stat; i++) {
385 if ((row = sql_fetch_row(mdb)) == NULL) {
386 Mmsg2(mdb->errmsg, _("Error fetching row %d: ERR=%s\n"), i, sql_strerror(mdb));
387 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
391 if (*VolumeNames[0] != 0) {
392 pm_strcat(VolumeNames, "|");
394 pm_strcat(VolumeNames, row[0]);
398 sql_free_result(mdb);
400 Mmsg(mdb->errmsg, _("No Volume for JobId %d found in Catalog.\n"), JobId);
407 * Find Volume parameters for a give JobId
408 * Returns: 0 on error or no Volumes found
409 * number of volumes on success
410 * List of Volumes and start/end file/blocks (malloced structure!)
412 * Returns: number of volumes on success
414 int db_get_job_volume_parameters(JCR *jcr, B_DB *mdb, JobId_t JobId, VOL_PARAMS **VolParams)
420 VOL_PARAMS *Vols = NULL;
424 "SELECT VolumeName,MediaType,FirstIndex,LastIndex,StartFile,"
425 "JobMedia.EndFile,StartBlock,JobMedia.EndBlock,"
426 "Slot,StorageId,InChanger"
427 " FROM JobMedia,Media WHERE JobMedia.JobId=%s"
428 " AND JobMedia.MediaId=Media.MediaId ORDER BY VolIndex,JobMediaId",
429 edit_int64(JobId, ed1));
431 Dmsg1(130, "VolNam=%s\n", mdb->cmd);
432 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
433 mdb->num_rows = sql_num_rows(mdb);
434 Dmsg1(200, "Num rows=%d\n", mdb->num_rows);
435 if (mdb->num_rows <= 0) {
436 Mmsg1(mdb->errmsg, _("No volumes found for JobId=%d\n"), JobId);
439 stat = mdb->num_rows;
442 *VolParams = Vols = (VOL_PARAMS *)malloc(stat * sizeof(VOL_PARAMS));
443 SId = (DBId_t *)malloc(stat * sizeof(DBId_t));
445 for (i=0; i < stat; i++) {
446 if ((row = sql_fetch_row(mdb)) == NULL) {
447 Mmsg2(mdb->errmsg, _("Error fetching row %d: ERR=%s\n"), i, sql_strerror(mdb));
448 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
453 uint32_t StartBlock, EndBlock, StartFile, EndFile;
454 bstrncpy(Vols[i].VolumeName, row[0], MAX_NAME_LENGTH);
455 bstrncpy(Vols[i].MediaType, row[1], MAX_NAME_LENGTH);
456 Vols[i].FirstIndex = str_to_uint64(row[2]);
457 Vols[i].LastIndex = str_to_uint64(row[3]);
458 StartFile = str_to_uint64(row[4]);
459 EndFile = str_to_uint64(row[5]);
460 StartBlock = str_to_uint64(row[6]);
461 EndBlock = str_to_uint64(row[7]);
462 Vols[i].StartAddr = (((uint64_t)StartFile)<<32) | StartBlock;
463 Vols[i].EndAddr = (((uint64_t)EndFile)<<32) | EndBlock;
464 Vols[i].Slot = str_to_uint64(row[8]);
465 StorageId = str_to_uint64(row[9]);
466 Vols[i].InChanger = str_to_uint64(row[10]);
467 Vols[i].Storage[0] = 0;
471 for (i=0; i < stat; i++) {
473 Mmsg(mdb->cmd, "SELECT Name from Storage WHERE StorageId=%s",
474 edit_int64(SId[i], ed1));
475 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
476 if ((row = sql_fetch_row(mdb)) && row[0]) {
477 bstrncpy(Vols[i].Storage, row[0], MAX_NAME_LENGTH);
486 sql_free_result(mdb);
495 * Get the number of pool records
497 * Returns: -1 on failure
500 int db_get_num_pool_records(JCR *jcr, B_DB *mdb)
505 Mmsg(mdb->cmd, "SELECT count(*) from Pool");
506 stat = get_sql_record_max(jcr, mdb);
512 * This function returns a list of all the Pool record ids.
513 * The caller must free ids if non-NULL.
515 * Returns 0: on failure
518 int db_get_pool_ids(JCR *jcr, B_DB *mdb, int *num_ids, uint32_t *ids[])
527 Mmsg(mdb->cmd, "SELECT PoolId FROM Pool");
528 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
529 *num_ids = sql_num_rows(mdb);
531 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
532 while ((row = sql_fetch_row(mdb)) != NULL) {
533 id[i++] = str_to_uint64(row[0]);
537 sql_free_result(mdb);
540 Mmsg(mdb->errmsg, _("Pool id select failed: ERR=%s\n"), sql_strerror(mdb));
541 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
549 * This function returns a list of all the Client record ids.
550 * The caller must free ids if non-NULL.
552 * Returns 0: on failure
555 int db_get_client_ids(JCR *jcr, B_DB *mdb, int *num_ids, uint32_t *ids[])
564 Mmsg(mdb->cmd, "SELECT ClientId FROM Client ORDER BY Name");
565 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
566 *num_ids = sql_num_rows(mdb);
568 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
569 while ((row = sql_fetch_row(mdb)) != NULL) {
570 id[i++] = str_to_uint64(row[0]);
574 sql_free_result(mdb);
577 Mmsg(mdb->errmsg, _("Client id select failed: ERR=%s\n"), sql_strerror(mdb));
578 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
589 * If the PoolId is non-zero, we get its record,
590 * otherwise, we search on the PoolName
592 * Returns: false on failure
595 bool db_get_pool_record(JCR *jcr, B_DB *mdb, POOL_DBR *pdbr)
602 if (pdbr->PoolId != 0) { /* find by id */
604 "SELECT PoolId,Name,NumVols,MaxVols,UseOnce,UseCatalog,AcceptAnyVolume,"
605 "AutoPrune,Recycle,VolRetention,VolUseDuration,MaxVolJobs,MaxVolFiles,"
606 "MaxVolBytes,PoolType,LabelType,LabelFormat,RecyclePoolId,ScratchPoolId,"
607 "ActionOnPurge FROM Pool WHERE Pool.PoolId=%s",
608 edit_int64(pdbr->PoolId, ed1));
609 } else { /* find by name */
611 "SELECT PoolId,Name,NumVols,MaxVols,UseOnce,UseCatalog,AcceptAnyVolume,"
612 "AutoPrune,Recycle,VolRetention,VolUseDuration,MaxVolJobs,MaxVolFiles,"
613 "MaxVolBytes,PoolType,LabelType,LabelFormat,RecyclePoolId,ScratchPoolId,"
614 "ActionOnPurge FROM Pool WHERE Pool.Name='%s'",
617 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
618 mdb->num_rows = sql_num_rows(mdb);
619 if (mdb->num_rows > 1) {
621 Mmsg1(mdb->errmsg, _("More than one Pool!: %s\n"),
622 edit_uint64(mdb->num_rows, ed1));
623 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
624 } else if (mdb->num_rows == 1) {
625 if ((row = sql_fetch_row(mdb)) == NULL) {
626 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
627 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
629 pdbr->PoolId = str_to_int64(row[0]);
630 bstrncpy(pdbr->Name, row[1]!=NULL?row[1]:"", sizeof(pdbr->Name));
631 pdbr->NumVols = str_to_int64(row[2]);
632 pdbr->MaxVols = str_to_int64(row[3]);
633 pdbr->UseOnce = str_to_int64(row[4]);
634 pdbr->UseCatalog = str_to_int64(row[5]);
635 pdbr->AcceptAnyVolume = str_to_int64(row[6]);
636 pdbr->AutoPrune = str_to_int64(row[7]);
637 pdbr->Recycle = str_to_int64(row[8]);
638 pdbr->VolRetention = str_to_int64(row[9]);
639 pdbr->VolUseDuration = str_to_int64(row[10]);
640 pdbr->MaxVolJobs = str_to_int64(row[11]);
641 pdbr->MaxVolFiles = str_to_int64(row[12]);
642 pdbr->MaxVolBytes = str_to_uint64(row[13]);
643 bstrncpy(pdbr->PoolType, row[14]!=NULL?row[14]:"", sizeof(pdbr->PoolType));
644 pdbr->LabelType = str_to_int64(row[15]);
645 bstrncpy(pdbr->LabelFormat, row[16]!=NULL?row[16]:"", sizeof(pdbr->LabelFormat));
646 pdbr->RecyclePoolId = str_to_int64(row[17]);
647 pdbr->ScratchPoolId = str_to_int64(row[18]);
648 pdbr->ActionOnPurge = str_to_int32(row[19]);
652 sql_free_result(mdb);
656 Mmsg(mdb->cmd, "SELECT count(*) from Media WHERE PoolId=%s",
657 edit_int64(pdbr->PoolId, ed1));
658 NumVols = get_sql_record_max(jcr, mdb);
659 Dmsg2(400, "Actual NumVols=%d Pool NumVols=%d\n", NumVols, pdbr->NumVols);
660 if (NumVols != pdbr->NumVols) {
661 pdbr->NumVols = NumVols;
662 db_update_pool_record(jcr, mdb, pdbr);
665 Mmsg(mdb->errmsg, _("Pool record not found in Catalog.\n"));
673 * If the ClientId is non-zero, we get its record,
674 * otherwise, we search on the Client Name
676 * Returns: 0 on failure
679 int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr)
686 if (cdbr->ClientId != 0) { /* find by id */
688 "SELECT ClientId,Name,Uname,AutoPrune,FileRetention,JobRetention "
689 "FROM Client WHERE Client.ClientId=%s",
690 edit_int64(cdbr->ClientId, ed1));
691 } else { /* find by name */
693 "SELECT ClientId,Name,Uname,AutoPrune,FileRetention,JobRetention "
694 "FROM Client WHERE Client.Name='%s'", cdbr->Name);
697 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
698 mdb->num_rows = sql_num_rows(mdb);
699 if (mdb->num_rows > 1) {
700 Mmsg1(mdb->errmsg, _("More than one Client!: %s\n"),
701 edit_uint64(mdb->num_rows, ed1));
702 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
703 } else if (mdb->num_rows == 1) {
704 if ((row = sql_fetch_row(mdb)) == NULL) {
705 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
706 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
708 cdbr->ClientId = str_to_int64(row[0]);
709 bstrncpy(cdbr->Name, row[1]!=NULL?row[1]:"", sizeof(cdbr->Name));
710 bstrncpy(cdbr->Uname, row[2]!=NULL?row[2]:"", sizeof(cdbr->Uname));
711 cdbr->AutoPrune = str_to_int64(row[3]);
712 cdbr->FileRetention = str_to_int64(row[4]);
713 cdbr->JobRetention = str_to_int64(row[5]);
717 Mmsg(mdb->errmsg, _("Client record not found in Catalog.\n"));
719 sql_free_result(mdb);
721 Mmsg(mdb->errmsg, _("Client record not found in Catalog.\n"));
730 * Returns: 0 on failure
733 int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr)
738 Mmsg(mdb->cmd, "SELECT MinValue,MaxValue,CurrentValue,WrapCounter "
739 "FROM Counters WHERE Counter='%s'", cr->Counter);
741 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
742 mdb->num_rows = sql_num_rows(mdb);
744 /* If more than one, report error, but return first row */
745 if (mdb->num_rows > 1) {
746 Mmsg1(mdb->errmsg, _("More than one Counter!: %d\n"), (int)(mdb->num_rows));
747 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
749 if (mdb->num_rows >= 1) {
750 if ((row = sql_fetch_row(mdb)) == NULL) {
751 Mmsg1(mdb->errmsg, _("error fetching Counter row: %s\n"), sql_strerror(mdb));
752 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
753 sql_free_result(mdb);
757 cr->MinValue = str_to_int64(row[0]);
758 cr->MaxValue = str_to_int64(row[1]);
759 cr->CurrentValue = str_to_int64(row[2]);
761 bstrncpy(cr->WrapCounter, row[3], sizeof(cr->WrapCounter));
763 cr->WrapCounter[0] = 0;
765 sql_free_result(mdb);
769 sql_free_result(mdb);
771 Mmsg(mdb->errmsg, _("Counter record: %s not found in Catalog.\n"), cr->Counter);
780 * If the FileSetId is non-zero, we get its record,
781 * otherwise, we search on the name
783 * Returns: 0 on failure
786 int db_get_fileset_record(JCR *jcr, B_DB *mdb, FILESET_DBR *fsr)
793 if (fsr->FileSetId != 0) { /* find by id */
795 "SELECT FileSetId,FileSet,MD5,CreateTime FROM FileSet "
796 "WHERE FileSetId=%s",
797 edit_int64(fsr->FileSetId, ed1));
798 } else { /* find by name */
800 "SELECT FileSetId,FileSet,MD5,CreateTime FROM FileSet "
801 "WHERE FileSet='%s' ORDER BY CreateTime DESC LIMIT 1", fsr->FileSet);
804 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
805 mdb->num_rows = sql_num_rows(mdb);
806 if (mdb->num_rows > 1) {
808 Mmsg1(mdb->errmsg, _("Error got %s FileSets but expected only one!\n"),
809 edit_uint64(mdb->num_rows, ed1));
810 sql_data_seek(mdb, mdb->num_rows-1);
812 if ((row = sql_fetch_row(mdb)) == NULL) {
813 Mmsg1(mdb->errmsg, _("FileSet record \"%s\" not found.\n"), fsr->FileSet);
815 fsr->FileSetId = str_to_int64(row[0]);
816 bstrncpy(fsr->FileSet, row[1]!=NULL?row[1]:"", sizeof(fsr->FileSet));
817 bstrncpy(fsr->MD5, row[2]!=NULL?row[2]:"", sizeof(fsr->MD5));
818 bstrncpy(fsr->cCreateTime, row[3]!=NULL?row[3]:"", sizeof(fsr->cCreateTime));
819 stat = fsr->FileSetId;
821 sql_free_result(mdb);
823 Mmsg(mdb->errmsg, _("FileSet record not found in Catalog.\n"));
831 * Get the number of Media records
833 * Returns: -1 on failure
836 int db_get_num_media_records(JCR *jcr, B_DB *mdb)
841 Mmsg(mdb->cmd, "SELECT count(*) from Media");
842 stat = get_sql_record_max(jcr, mdb);
848 * This function returns a list of all the Media record ids for
849 * the current Pool, the correct Media Type, Recyle, Enabled, StorageId, VolBytes
850 * VolumeName if specified
851 * The caller must free ids if non-NULL.
853 * Returns false: on failure
856 bool db_get_media_ids(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr, int *num_ids, uint32_t *ids[])
863 char buf[MAX_NAME_LENGTH*3]; /* Can contain MAX_NAME_LENGTH*2+1 + AND ....='' */
864 char esc[MAX_NAME_LENGTH*2+1];
869 Mmsg(mdb->cmd, "SELECT DISTINCT MediaId FROM Media WHERE Recycle=%d AND Enabled=%d ",
870 mr->Recycle, mr->Enabled);
872 if (*mr->MediaType) {
873 db_escape_string(jcr, mdb, esc, mr->MediaType, strlen(mr->MediaType));
874 bsnprintf(buf, sizeof(buf), "AND MediaType='%s' ", esc);
875 pm_strcat(mdb->cmd, buf);
879 bsnprintf(buf, sizeof(buf), "AND StorageId=%s ", edit_uint64(mr->StorageId, ed1));
880 pm_strcat(mdb->cmd, buf);
884 bsnprintf(buf, sizeof(buf), "AND PoolId=%s ", edit_uint64(mr->PoolId, ed1));
885 pm_strcat(mdb->cmd, buf);
889 bsnprintf(buf, sizeof(buf), "AND VolBytes > %s ", edit_uint64(mr->VolBytes, ed1));
890 pm_strcat(mdb->cmd, buf);
893 if (*mr->VolumeName) {
894 db_escape_string(jcr, mdb, esc, mr->VolumeName, strlen(mr->VolumeName));
895 bsnprintf(buf, sizeof(buf), "AND VolumeName = '%s' ", esc);
896 pm_strcat(mdb->cmd, buf);
899 if (*mr->VolStatus) {
900 db_escape_string(jcr, mdb, esc, mr->VolStatus, strlen(mr->VolStatus));
901 bsnprintf(buf, sizeof(buf), "AND VolStatus = '%s' ", esc);
902 pm_strcat(mdb->cmd, buf);
905 Dmsg1(100, "q=%s\n", mdb->cmd);
907 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
908 *num_ids = sql_num_rows(mdb);
910 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
911 while ((row = sql_fetch_row(mdb)) != NULL) {
912 id[i++] = str_to_uint64(row[0]);
916 sql_free_result(mdb);
919 Mmsg(mdb->errmsg, _("Media id select failed: ERR=%s\n"), sql_strerror(mdb));
920 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
929 * This function returns a list of all the DBIds that are returned
932 * Returns false: on failure
935 bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids)
943 if (QUERY_DB(jcr, mdb, query.c_str())) {
944 ids.num_ids = sql_num_rows(mdb);
945 if (ids.num_ids > 0) {
946 if (ids.max_ids < ids.num_ids) {
948 ids.DBId = (DBId_t *)malloc(ids.num_ids * sizeof(DBId_t));
950 while ((row = sql_fetch_row(mdb)) != NULL) {
951 ids.DBId[i++] = str_to_uint64(row[0]);
954 sql_free_result(mdb);
957 Mmsg(mdb->errmsg, _("query dbids failed: ERR=%s\n"), sql_strerror(mdb));
958 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
968 * Returns: false: on failure
971 bool db_get_media_record(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr)
978 if (mr->MediaId == 0 && mr->VolumeName[0] == 0) {
979 Mmsg(mdb->cmd, "SELECT count(*) from Media");
980 mr->MediaId = get_sql_record_max(jcr, mdb);
984 if (mr->MediaId != 0) { /* find by id */
985 Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks,"
986 "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes,"
987 "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs,"
988 "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger,"
989 "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId,"
990 "Enabled,LocationId,RecycleCount,InitialWrite,"
991 "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime,ActionOnPurge "
992 "FROM Media WHERE MediaId=%s",
993 edit_int64(mr->MediaId, ed1));
994 } else { /* find by name */
995 Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks,"
996 "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes,"
997 "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs,"
998 "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger,"
999 "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId,"
1000 "Enabled,LocationId,RecycleCount,InitialWrite,"
1001 "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime,ActionOnPurge "
1002 "FROM Media WHERE VolumeName='%s'", mr->VolumeName);
1005 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
1007 mdb->num_rows = sql_num_rows(mdb);
1008 if (mdb->num_rows > 1) {
1009 Mmsg1(mdb->errmsg, _("More than one Volume!: %s\n"),
1010 edit_uint64(mdb->num_rows, ed1));
1011 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
1012 } else if (mdb->num_rows == 1) {
1013 if ((row = sql_fetch_row(mdb)) == NULL) {
1014 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
1015 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
1018 mr->MediaId = str_to_int64(row[0]);
1019 bstrncpy(mr->VolumeName, row[1]!=NULL?row[1]:"", sizeof(mr->VolumeName));
1020 mr->VolJobs = str_to_int64(row[2]);
1021 mr->VolFiles = str_to_int64(row[3]);
1022 mr->VolBlocks = str_to_int64(row[4]);
1023 mr->VolBytes = str_to_uint64(row[5]);
1024 mr->VolMounts = str_to_int64(row[6]);
1025 mr->VolErrors = str_to_int64(row[7]);
1026 mr->VolWrites = str_to_int64(row[8]);
1027 mr->MaxVolBytes = str_to_uint64(row[9]);
1028 mr->VolCapacityBytes = str_to_uint64(row[10]);
1029 bstrncpy(mr->MediaType, row[11]!=NULL?row[11]:"", sizeof(mr->MediaType));
1030 bstrncpy(mr->VolStatus, row[12]!=NULL?row[12]:"", sizeof(mr->VolStatus));
1031 mr->PoolId = str_to_int64(row[13]);
1032 mr->VolRetention = str_to_uint64(row[14]);
1033 mr->VolUseDuration = str_to_uint64(row[15]);
1034 mr->MaxVolJobs = str_to_int64(row[16]);
1035 mr->MaxVolFiles = str_to_int64(row[17]);
1036 mr->Recycle = str_to_int64(row[18]);
1037 mr->Slot = str_to_int64(row[19]);
1038 bstrncpy(mr->cFirstWritten, row[20]!=NULL?row[20]:"", sizeof(mr->cFirstWritten));
1039 mr->FirstWritten = (time_t)str_to_utime(mr->cFirstWritten);
1040 bstrncpy(mr->cLastWritten, row[21]!=NULL?row[21]:"", sizeof(mr->cLastWritten));
1041 mr->LastWritten = (time_t)str_to_utime(mr->cLastWritten);
1042 mr->InChanger = str_to_uint64(row[22]);
1043 mr->EndFile = str_to_uint64(row[23]);
1044 mr->EndBlock = str_to_uint64(row[24]);
1045 mr->VolParts = str_to_int64(row[25]);
1046 mr->LabelType = str_to_int64(row[26]);
1047 bstrncpy(mr->cLabelDate, row[27]!=NULL?row[27]:"", sizeof(mr->cLabelDate));
1048 mr->LabelDate = (time_t)str_to_utime(mr->cLabelDate);
1049 mr->StorageId = str_to_int64(row[28]);
1050 mr->Enabled = str_to_int64(row[29]);
1051 mr->LocationId = str_to_int64(row[30]);
1052 mr->RecycleCount = str_to_int64(row[31]);
1053 bstrncpy(mr->cInitialWrite, row[32]!=NULL?row[32]:"", sizeof(mr->cInitialWrite));
1054 mr->InitialWrite = (time_t)str_to_utime(mr->cInitialWrite);
1055 mr->ScratchPoolId = str_to_int64(row[33]);
1056 mr->RecyclePoolId = str_to_int64(row[34]);
1057 mr->VolReadTime = str_to_int64(row[35]);
1058 mr->VolWriteTime = str_to_int64(row[36]);
1059 mr->ActionOnPurge = str_to_int32(row[37]);
1064 if (mr->MediaId != 0) {
1065 Mmsg1(mdb->errmsg, _("Media record MediaId=%s not found.\n"),
1066 edit_int64(mr->MediaId, ed1));
1068 Mmsg1(mdb->errmsg, _("Media record for Volume \"%s\" not found.\n"),
1072 sql_free_result(mdb);
1074 if (mr->MediaId != 0) {
1075 Mmsg(mdb->errmsg, _("Media record for MediaId=%u not found in Catalog.\n"),
1078 Mmsg(mdb->errmsg, _("Media record for Vol=%s not found in Catalog.\n"),
1086 * Find the last "accurate" backup state (that can take deleted files in
1088 * 1) Get all files with jobid in list (F subquery)
1089 * Get all files in BaseFiles with jobid in list
1090 * 2) Take only the last version of each file (Temp subquery) => accurate list
1092 * 3) Join the result to file table to get fileindex, jobid and lstat information
1094 * TODO: See if we can do the SORT only if needed (as an argument)
1096 bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids,
1097 DB_RESULT_HANDLER *result_handler, void *ctx)
1101 Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
1105 POOL_MEM buf(PM_MESSAGE);
1107 #define new_db_get_file_list
1108 #ifdef new_db_get_file_list
1109 POOL_MEM buf2(PM_MESSAGE);
1110 Mmsg(buf2, select_recent_version_with_basejob[db_type],
1111 jobids, jobids, jobids, jobids);
1113 "SELECT Path.Path, Filename.Name, Temp.FileIndex, Temp.JobId, LStat, MD5 "
1114 "FROM ( %s ) AS Temp "
1115 "JOIN Filename ON (Filename.FilenameId = Temp.FilenameId) "
1116 "JOIN Path ON (Path.PathId = Temp.PathId) "
1117 "WHERE FileIndex > 0 "
1118 "ORDER BY Temp.JobId, FileIndex ASC",/* Return sorted by JobId, */
1119 /* FileIndex for restore code */
1123 * I am not sure that this works the same as the code in ua_restore.c but it
1124 * is very similar. The accurate-test fails in a restore. Bad file count.
1126 Mmsg(buf, uar_sel_files, jobids);
1129 return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
1133 * This procedure gets the base jobid list used by jobids,
1135 bool db_get_used_base_jobids(JCR *jcr, B_DB *mdb,
1136 POOLMEM *jobids, db_list_ctx *result)
1140 "SELECT DISTINCT BaseJobId "
1141 " FROM Job JOIN BaseFiles USING (JobId) "
1142 " WHERE Job.HasBase = 1 "
1143 " AND Job.JobId IN (%s) ", jobids);
1144 return db_sql_query(mdb, buf.c_str(), db_list_handler, result);
1148 * The decision do change an incr/diff was done before
1150 * Differential : get the last full id
1151 * Incremental : get the last full + last diff + last incr(s) ids
1153 * If you specify jr->StartTime, it will be used to limit the search
1154 * in the time. (usually now)
1156 * TODO: look and merge from ua_restore.c
1158 bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb,
1159 JOB_DBR *jr, db_list_ctx *jobids)
1162 char clientid[50], jobid[50], filesetid[50];
1163 char date[MAX_TIME_LENGTH];
1164 POOL_MEM query(PM_FNAME);
1166 /* Take the current time as upper limit if nothing else specified */
1167 utime_t StartTime = (jr->StartTime)?jr->StartTime:time(NULL);
1169 bstrutime(date, sizeof(date), StartTime + 1);
1170 jobids->list[0] = 0;
1173 /* First, find the last good Full backup for this job/client/fileset */
1175 "CREATE TABLE btemp3%s AS "
1176 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1177 "FROM Job JOIN FileSet USING (FileSetId) "
1178 "WHERE ClientId = %s "
1179 "AND Level='F' AND JobStatus IN ('T','W') AND Type='B' "
1180 "AND StartTime<'%s' "
1181 "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1182 "ORDER BY Job.JobTDate DESC LIMIT 1",
1183 edit_uint64(jcr->JobId, jobid),
1184 edit_uint64(jr->ClientId, clientid),
1186 edit_uint64(jr->FileSetId, filesetid));
1188 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1192 if (jr->JobLevel == L_INCREMENTAL || jr->JobLevel == L_VIRTUAL_FULL) {
1193 /* Now, find the last differential backup after the last full */
1195 "INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
1196 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1197 "FROM Job JOIN FileSet USING (FileSetId) "
1198 "WHERE ClientId = %s "
1199 "AND Level='D' AND JobStatus IN ('T','W') AND Type='B' "
1200 "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
1201 "AND StartTime < '%s' "
1202 "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1203 "ORDER BY Job.JobTDate DESC LIMIT 1 ",
1210 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1214 /* We just have to take all incremental after the last Full/Diff */
1216 "INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
1217 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1218 "FROM Job JOIN FileSet USING (FileSetId) "
1219 "WHERE ClientId = %s "
1220 "AND Level='I' AND JobStatus IN ('T','W') AND Type='B' "
1221 "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
1222 "AND StartTime < '%s' "
1223 "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1224 "ORDER BY Job.JobTDate DESC ",
1230 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1235 /* build a jobid list ie: 1,2,3,4 */
1236 Mmsg(query, "SELECT JobId FROM btemp3%s ORDER by JobTDate", jobid);
1237 db_sql_query(mdb, query.c_str(), db_list_handler, jobids);
1238 Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids->list);
1242 Mmsg(query, "DROP TABLE btemp3%s", jobid);
1243 db_sql_query(mdb, query.c_str(), NULL, NULL);
1248 bool db_get_base_file_list(JCR *jcr, B_DB *mdb,
1249 DB_RESULT_HANDLER *result_handler, void *ctx)
1251 POOL_MEM buf(PM_MESSAGE);
1254 "SELECT Path, Name, FileIndex, JobId, LStat, MD5 "
1255 "FROM new_basefile%lld ORDER BY JobId, FileIndex ASC",
1256 (uint64_t) jcr->JobId);
1258 return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
1261 bool db_get_base_jobid(JCR *jcr, B_DB *mdb, JOB_DBR *jr, JobId_t *jobid)
1263 POOL_MEM query(PM_FNAME);
1266 char date[MAX_TIME_LENGTH];
1270 // char clientid[50], filesetid[50];
1272 StartTime = (jr->StartTime)?jr->StartTime:time(NULL);
1273 bstrutime(date, sizeof(date), StartTime + 1);
1275 /* we can take also client name, fileset, etc... */
1278 "SELECT JobId, Job, StartTime, EndTime, JobTDate, PurgedFiles "
1280 // "JOIN FileSet USING (FileSetId) JOIN Client USING (ClientId) "
1281 "WHERE Job.Name = '%s' "
1282 "AND Level='B' AND JobStatus IN ('T','W') AND Type='B' "
1283 // "AND FileSet.FileSet= '%s' "
1284 // "AND Client.Name = '%s' "
1285 "AND StartTime<'%s' "
1286 "ORDER BY Job.JobTDate DESC LIMIT 1",
1288 // edit_uint64(jr->ClientId, clientid),
1289 // edit_uint64(jr->FileSetId, filesetid));
1292 Dmsg1(10, "db_get_base_jobid q=%s\n", query.c_str());
1293 if (!db_sql_query(mdb, query.c_str(), db_int64_handler, &lctx)) {
1296 *jobid = (JobId_t) lctx.value;
1298 Dmsg1(10, "db_get_base_jobid=%lld\n", *jobid);
1305 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI */