2 Bacula® - The Network Backup Solution
4 Copyright (C) 2009-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 routines specific to Ingres
30 * These are Ingres specific routines
32 * Stefan Reddig, June 2009 with help of Marco van Wieringen April 2010
38 EXEC SQL INCLUDE SQLCA;
39 EXEC SQL INCLUDE SQLDA;
48 * ---Implementations---
50 int INGgetCols(INGconn *dbconn, const char *query, bool explicit_commit)
52 EXEC SQL BEGIN DECLARE SECTION;
55 EXEC SQL END DECLARE SECTION;
59 sqlda = (IISQLDA *)malloc(IISQDA_HEAD_SIZE + IISQDA_VAR_SIZE);
60 memset(sqlda, 0, (IISQDA_HEAD_SIZE + IISQDA_VAR_SIZE));
64 stmt = bstrdup(query);
66 EXEC SQL WHENEVER SQLERROR GOTO bail_out;
69 * Switch to the correct default session for this thread.
71 sess_id = dbconn->session_id;
72 EXEC SQL SET_SQL (SESSION = :sess_id);
74 EXEC SQL PREPARE s1 INTO :sqlda FROM :stmt;
76 EXEC SQL WHENEVER SQLERROR CONTINUE;
82 * If explicit_commit is set we commit our work now.
84 if (explicit_commit) {
89 * Switch to no default session for this thread.
91 EXEC SQL SET_SQL (SESSION = NONE);
97 static inline IISQLDA *INGgetDescriptor(int numCols, const char *query)
99 EXEC SQL BEGIN DECLARE SECTION;
101 EXEC SQL END DECLARE SECTION;
105 sqlda = (IISQLDA *)malloc(IISQDA_HEAD_SIZE + (numCols * IISQDA_VAR_SIZE));
106 memset(sqlda, 0, (IISQDA_HEAD_SIZE + (numCols * IISQDA_VAR_SIZE)));
108 sqlda->sqln = numCols;
110 stmt = bstrdup(query);
112 EXEC SQL PREPARE s2 INTO :sqlda FROM :stmt;
114 for (i = 0; i < sqlda->sqld; ++i) {
116 * Negative type indicates nullable columns, so an indicator
117 * is allocated, otherwise it's null
119 if (sqlda->sqlvar[i].sqltype > 0) {
120 sqlda->sqlvar[i].sqlind = NULL;
122 sqlda->sqlvar[i].sqlind = (short *)malloc(sizeof(short));
125 * Alloc space for variable like indicated in sqllen
126 * for date types sqllen is always 0 -> allocate by type
128 switch (abs(sqlda->sqlvar[i].sqltype)) {
130 sqlda->sqlvar[i].sqldata = (char *)malloc(IISQ_TSW_LEN);
133 sqlda->sqlvar[i].sqldata = (char *)malloc(IISQ_TSWO_LEN);
135 case IISQ_TSTMP_TYPE:
136 sqlda->sqlvar[i].sqldata = (char *)malloc(IISQ_TSTMP_LEN);
140 * plus one to avoid zero mem allocs
142 sqlda->sqlvar[i].sqldata = (char *)malloc(sqlda->sqlvar[i].sqllen + 1);
151 static void INGfreeDescriptor(IISQLDA *sqlda)
159 for (i = 0; i < sqlda->sqld; ++i) {
160 if (sqlda->sqlvar[i].sqldata) {
161 free(sqlda->sqlvar[i].sqldata);
163 if (sqlda->sqlvar[i].sqlind) {
164 free(sqlda->sqlvar[i].sqlind);
170 static inline int INGgetTypeSize(IISQLVAR *ingvar)
174 switch (ingvar->sqltype) {
188 inglength = ingvar->sqllen;
195 static inline INGresult *INGgetINGresult(int numCols, const char *query)
200 ing_res = (INGresult *)malloc(sizeof(INGresult));
201 memset(ing_res, 0, sizeof(INGresult));
203 if ((ing_res->sqlda = INGgetDescriptor(numCols, query)) == NULL) {
207 ing_res->num_fields = ing_res->sqlda->sqld;
208 ing_res->num_rows = 0;
209 ing_res->first_row = NULL;
210 ing_res->status = ING_EMPTY_RESULT;
211 ing_res->act_row = NULL;
213 if (ing_res->num_fields) {
214 ing_res->fields = (INGRES_FIELD *)malloc(sizeof(INGRES_FIELD) * ing_res->num_fields);
215 memset(ing_res->fields, 0, sizeof(INGRES_FIELD) * ing_res->num_fields);
217 for (i = 0; i < ing_res->num_fields; ++i) {
218 ing_res->fields[i].name = (char *)malloc(ing_res->sqlda->sqlvar[i].sqlname.sqlnamel + 1);
219 bstrncpy(ing_res->fields[i].name, ing_res->sqlda->sqlvar[i].sqlname.sqlnamec, ing_res->sqlda->sqlvar[i].sqlname.sqlnamel + 1);
220 ing_res->fields[i].name[ing_res->sqlda->sqlvar[i].sqlname.sqlnamel] = '\0';
221 ing_res->fields[i].max_length = INGgetTypeSize(&ing_res->sqlda->sqlvar[i]);
222 ing_res->fields[i].type = abs(ing_res->sqlda->sqlvar[i].sqltype);
223 ing_res->fields[i].flags = (ing_res->sqlda->sqlvar[i].sqltype < 0) ? 1 : 0;
230 static inline void INGfreeRowSpace(ING_ROW *row, IISQLDA *sqlda)
234 if (row == NULL || sqlda == NULL) {
238 for (i = 0; i < sqlda->sqld; ++i) {
239 if (row->sqlvar[i].sqldata) {
240 free(row->sqlvar[i].sqldata);
242 if (row->sqlvar[i].sqlind) {
243 free(row->sqlvar[i].sqlind);
250 static void INGfreeINGresult(INGresult *ing_res)
261 * Use of rows is a nasty workaround til I find the reason,
262 * why aggregates like max() don't work
264 rows = ing_res->num_rows;
265 ing_res->act_row = ing_res->first_row;
266 while (ing_res->act_row != NULL && rows > 0) {
267 rowtemp = ing_res->act_row->next;
268 INGfreeRowSpace(ing_res->act_row, ing_res->sqlda);
269 ing_res->act_row = rowtemp;
273 if (ing_res->fields) {
274 for (i = 0; i < ing_res->num_fields; ++i) {
275 free(ing_res->fields[i].name);
278 free(ing_res->fields);
281 INGfreeDescriptor(ing_res->sqlda);
286 static inline ING_ROW *INGgetRowSpace(INGresult *ing_res)
289 unsigned short len; /* used for VARCHAR type length */
290 unsigned short th, tm, ts;
294 IISQLVAR *vars = NULL;
296 row = (ING_ROW *)malloc(sizeof(ING_ROW));
297 memset(row, 0, sizeof(ING_ROW));
299 sqlda = ing_res->sqlda;
300 vars = (IISQLVAR *)malloc(sizeof(IISQLVAR) * sqlda->sqld);
301 memset(vars, 0, sizeof(IISQLVAR) * sqlda->sqld);
306 for (i = 0; i < sqlda->sqld; ++i) {
308 * Make strings out of the data, then the space and assign
309 * (why string? at least it seems that way, looking into the sources)
311 vars[i].sqlind = (short *)malloc(sizeof(short));
312 if (sqlda->sqlvar[i].sqlind) {
313 memcpy(vars[i].sqlind,sqlda->sqlvar[i].sqlind,sizeof(short));
315 *vars[i].sqlind = NULL;
318 * if sqlind pointer exists AND points to -1 -> column is 'null'
320 if ( *vars[i].sqlind && (*vars[i].sqlind == -1)) {
321 vars[i].sqldata = NULL;
323 switch (ing_res->fields[i].type) {
326 case IISQ_VBYTE_TYPE:
327 case IISQ_LBYTE_TYPE:
328 case IISQ_NVCHR_TYPE:
329 case IISQ_LNVCHR_TYPE:
330 len = ((ING_VARCHAR *)sqlda->sqlvar[i].sqldata)->len;
331 vars[i].sqldata = (char *)malloc(len + 1);
332 memcpy(vars[i].sqldata,sqlda->sqlvar[i].sqldata + 2,len);
333 vars[i].sqldata[len] = '\0';
338 vars[i].sqldata = (char *)malloc(ing_res->fields[i].max_length + 1);
339 memcpy(vars[i].sqldata,sqlda->sqlvar[i].sqldata,sqlda->sqlvar[i].sqllen);
340 vars[i].sqldata[ing_res->fields[i].max_length] = '\0';
343 switch (sqlda->sqlvar[i].sqllen) {
345 vars[i].sqldata = (char *)malloc(6);
346 memset(vars[i].sqldata, 0, 6);
347 bsnprintf(vars[i].sqldata, 6, "%d",*(int16_t *)sqlda->sqlvar[i].sqldata);
350 vars[i].sqldata = (char *)malloc(11);
351 memset(vars[i].sqldata, 0, 11);
352 bsnprintf(vars[i].sqldata, 11, "%ld",*(int32_t *)sqlda->sqlvar[i].sqldata);
355 vars[i].sqldata = (char *)malloc(20);
356 memset(vars[i].sqldata, 0, 20);
357 bsnprintf(vars[i].sqldata, 20, "%lld",*(int64_t *)sqlda->sqlvar[i].sqldata);
361 case IISQ_TSTMP_TYPE:
362 vars[i].sqldata = (char *)malloc(IISQ_TSTMP_LEN + 1);
363 vars[i].sqldata[IISQ_TSTMP_LEN] = '\0';
366 tsp = (ING_TIMESTAMP *)sqlda->sqlvar[i].sqldata;
367 th = tsp->secs / 3600; /* hours */
368 tm = tsp->secs % 3600; /* remaining seconds */
369 tm = tm / 60; /* minutes */
370 ts = tsp->secs - (th * 3600) - (tm * 60); /* seconds */
371 vars[i].sqldata = (char *)malloc(IISQ_TSTMP_LEN + 1);
372 bsnprintf(vars[i].sqldata, IISQ_TSWO_LEN + 1,
373 "%04u-%02u-%02u %02u:%02u:%02u",
374 tsp->year, tsp->month, tsp->day, th, tm, ts);
377 tsp = (ING_TIMESTAMP *)sqlda->sqlvar[i].sqldata;
378 th = tsp->secs / 3600; /* hours */
379 tm = tsp->secs % 3600; /* remaining seconds */
380 tm = tm / 60; /* minutes */
381 ts = tsp->secs - (th * 3600) - (tm * 60); /* seconds */
382 vars[i].sqldata = (char *)malloc(IISQ_TSW_LEN + 1);
383 bsnprintf(vars[i].sqldata, IISQ_TSW_LEN + 1,
384 "%04u-%02u-%02u %02u:%02u:%02u",
385 tsp->year, tsp->month, tsp->day, th, tm, ts);
388 Jmsg(NULL, M_FATAL, 0,
389 "INGgetRowSpace: encountered unhandled database datatype %d please report this as a bug\n",
390 ing_res->fields[i].type);
398 static inline int INGfetchAll(INGresult *ing_res)
404 desc = ing_res->sqlda;
406 EXEC SQL WHENEVER SQLERROR GOTO bail_out;
408 EXEC SQL DECLARE c2 CURSOR FOR s2;
411 EXEC SQL WHENEVER SQLERROR CONTINUE;
415 EXEC SQL FETCH c2 USING DESCRIPTOR :desc;
417 if (sqlca.sqlcode == 0 || sqlca.sqlcode == -40202) {
419 * Allocate space for fetched row
421 row = INGgetRowSpace(ing_res);
424 * Initialize list when encountered first time
426 if (ing_res->first_row == 0) {
427 ing_res->first_row = row; /* head of the list */
428 ing_res->first_row->next = NULL;
429 ing_res->act_row = ing_res->first_row;
432 ing_res->act_row->next = row; /* append row to old act_row */
433 ing_res->act_row = row; /* set row as act_row */
434 row->row_number = linecount++;
436 } while ( (sqlca.sqlcode == 0) || (sqlca.sqlcode == -40202) );
440 ing_res->status = ING_COMMAND_OK;
441 ing_res->num_rows = linecount;
447 static inline ING_STATUS INGresultStatus(INGresult *ing_res)
449 if (ing_res == NULL) {
450 return ING_NO_RESULT;
452 return ing_res->status;
456 static void INGrowSeek(INGresult *ing_res, int row_number)
458 ING_ROW *trow = NULL;
460 if (ing_res->act_row->row_number == row_number) {
465 * TODO: real error handling
467 if (row_number < 0 || row_number > ing_res->num_rows) {
471 for (trow = ing_res->first_row; trow->row_number != row_number; trow = trow->next) ;
472 ing_res->act_row = trow;
474 * Note - can be null - if row_number not found, right?
478 char *INGgetvalue(INGresult *ing_res, int row_number, int column_number)
480 if (row_number != ing_res->act_row->row_number) {
481 INGrowSeek(ing_res, row_number);
484 return ing_res->act_row->sqlvar[column_number].sqldata;
487 bool INGgetisnull(INGresult *ing_res, int row_number, int column_number)
489 if (row_number != ing_res->act_row->row_number) {
490 INGrowSeek(ing_res, row_number);
493 return (*ing_res->act_row->sqlvar[column_number].sqlind == -1) ? true : false;
496 int INGntuples(const INGresult *ing_res)
498 return ing_res->num_rows;
501 int INGnfields(const INGresult *ing_res)
503 return ing_res->num_fields;
506 char *INGfname(const INGresult *ing_res, int column_number)
508 if ((column_number > ing_res->num_fields) || (column_number < 0)) {
511 return ing_res->fields[column_number].name;
515 short INGftype(const INGresult *ing_res, int column_number)
517 return ing_res->fields[column_number].type;
520 int INGexec(INGconn *dbconn, const char *query, bool explicit_commit)
522 EXEC SQL BEGIN DECLARE SECTION;
527 EXEC SQL END DECLARE SECTION;
530 stmt = bstrdup(query);
532 EXEC SQL WHENEVER SQLERROR GOTO bail_out;
535 * Switch to the correct default session for this thread.
537 sess_id = dbconn->session_id;
538 EXEC SQL SET_SQL (SESSION = :sess_id);
540 EXEC SQL EXECUTE IMMEDIATE :stmt;
541 EXEC SQL INQUIRE_INGRES(:rowcount = ROWCOUNT);
544 * See if the negative rowcount is due to errors.
547 EXEC SQL INQUIRE_INGRES(:errors = DBMSERROR);
550 * If the number of errors is 0 we got a negative rowcount
551 * because the statement we executed doesn't give a rowcount back.
552 * Lets pretend we have a rowcount of 1 then.
559 EXEC SQL WHENEVER SQLERROR CONTINUE;
563 * If explicit_commit is set we commit our work now.
565 if (explicit_commit) {
566 EXEC SQL COMMIT WORK;
570 * Switch to no default session for this thread.
572 EXEC SQL SET_SQL (SESSION = NONE);
577 INGresult *INGquery(INGconn *dbconn, const char *query, bool explicit_commit)
580 * TODO: error handling
582 INGresult *ing_res = NULL;
585 EXEC SQL BEGIN DECLARE SECTION;
587 EXEC SQL END DECLARE SECTION;
589 cols = INGgetCols(dbconn, query, explicit_commit);
592 * Switch to the correct default session for this thread.
594 sess_id = dbconn->session_id;
595 EXEC SQL SET_SQL (SESSION = :sess_id);
597 ing_res = INGgetINGresult(cols, query);
602 rows = INGfetchAll(ing_res);
605 INGfreeINGresult(ing_res);
612 * If explicit_commit is set we commit our work now.
614 if (explicit_commit) {
615 EXEC SQL COMMIT WORK;
619 * Switch to no default session for this thread.
621 EXEC SQL SET_SQL (SESSION = NONE);
625 void INGclear(INGresult *ing_res)
627 if (ing_res == NULL) {
631 INGfreeINGresult(ing_res);
634 void INGcommit(const INGconn *dbconn)
636 EXEC SQL BEGIN DECLARE SECTION;
638 EXEC SQL END DECLARE SECTION;
640 if (dbconn != NULL) {
642 * Switch to the correct default session for this thread.
644 sess_id = dbconn->session_id;
645 EXEC SQL SET_SQL (SESSION = :sess_id);
650 EXEC SQL COMMIT WORK;
653 * Switch to no default session for this thread.
655 EXEC SQL SET_SQL (SESSION = NONE);
659 INGconn *INGconnectDB(char *dbname, char *user, char *passwd, int session_id)
661 EXEC SQL BEGIN DECLARE SECTION;
663 char *ingdbuser = NULL;
664 char *ingdbpasswd = NULL;
666 EXEC SQL END DECLARE SECTION;
667 INGconn *dbconn = NULL;
669 if (dbname == NULL || strlen(dbname) == 0) {
673 sess_id = session_id;
676 EXEC SQL WHENEVER SQLERROR GOTO bail_out;
680 if (passwd != NULL) {
681 ingdbpasswd = passwd;
685 IDENTIFIED BY :ingdbuser
686 DBMS_PASSWORD = :ingdbpasswd;
691 IDENTIFIED BY :ingdbuser;
699 EXEC SQL WHENEVER SQLERROR CONTINUE;
701 dbconn = (INGconn *)malloc(sizeof(INGconn));
702 memset(dbconn, 0, sizeof(INGconn));
704 dbconn->dbname = bstrdup(ingdbname);
706 dbconn->user = bstrdup(ingdbuser);
707 dbconn->password = bstrdup(ingdbpasswd);
709 dbconn->session_id = sess_id;
710 dbconn->msg = (char *)malloc(257);
711 memset(dbconn->msg, 0, 257);
714 * Switch to no default session for this thread undo default settings from SQL CONNECT.
716 EXEC SQL SET_SQL (SESSION = NONE);
722 void INGsetDefaultLockingMode(INGconn *dbconn)
725 * Set the default Ingres session locking mode:
727 * SET LOCKMODE provides four different parameters to govern
728 * the nature of locking in an INGRES session:
730 * Level: This refers to the level of granularity desired when
731 * the table is accessed. You can specify any of the following
734 * row Specifies locking at the level of the row (subject to
735 * escalation criteria; see below)
736 * page Specifies locking at the level of the data page (subject to
737 * escalation criteria; see below)
738 * table Specifies table-level locking in the database
739 * session Specifies the current default for your INGRES session
740 * system Specifies that INGRES will start with page-level locking,
741 * unless it estimates that more than Maxlocks pages will be
742 * referenced, in which case table-level locking will be used.
744 * Readlock: This refers to locking in situations where table access
745 * is required for reading data only (as opposed to updating
746 * data). You can specify any of the following Readlock modes:
748 * nolock Specifies no locking when reading data
749 * shared Specifies the default mode of locking when reading data
750 * exclusive Specifies exclusive locking when reading data (useful in
751 * "select-for-update" processing within a multi-statement
753 * system Specifies the general Readlock default for the INGRES system
755 * Maxlocks: This refers to an escalation factor, or number of locks on
756 * data pages, at which locking escalates from page-level
757 * to table-level. The number of locks available to you is
758 * dependent upon your system configuration. You can specify the
759 * following Maxlocks escalation factors:
761 * n A specific (integer) number of page locks to allow before
762 * escalating to table-level locking. The default "n" is 10,
763 * and "n" must be greater than 0.
764 * session Specifies the current Maxlocks default for your INGRES
766 * system Specifies the general Maxlocks default for the INGRES system
768 * Note: If you specify page-level locking, and the number of locks granted
769 * during a query exceeds the system-wide lock limit, or if the operating
770 * system's locking resources are depleted, locking escalates to table-level.
771 * This escalation occurs automatically and is independent of the user.
773 * Timeout: This refers to a time limit, expressed in seconds, for which
774 * a lock request should remain pending. If INGRES cannot grant the lock
775 * request within the specified time, then the query that requested the
776 * lock aborts. You can specify the following timeout characteristics:
778 * n A specific (integer) number of seconds to wait for a lock
779 * (setting "n" to 0 requires INGRES to wait indefinitely for
781 * session Specifies the current timeout default for your INGRES
782 * session (which is also the INGRES default)
783 * system Specifies the general timeout default for the INGRES system
786 EXEC SQL BEGIN DECLARE SECTION;
788 EXEC SQL END DECLARE SECTION;
790 if (dbconn != NULL) {
792 * Switch to the correct default session for this thread.
794 sess_id = dbconn->session_id;
795 EXEC SQL SET_SQL (SESSION = :sess_id);
797 EXEC SQL SET LOCKMODE SESSION WHERE level = row, readlock = nolock;
800 * Switch to no default session for this thread.
802 EXEC SQL SET_SQL (SESSION = NONE);
806 void INGdisconnectDB(INGconn *dbconn)
808 EXEC SQL BEGIN DECLARE SECTION;
810 EXEC SQL END DECLARE SECTION;
812 if (dbconn != NULL) {
813 sess_id = dbconn->session_id;
814 EXEC SQL DISCONNECT SESSION :sess_id;
816 free(dbconn->dbname);
820 if (dbconn->password) {
821 free(dbconn->password);
828 char *INGerrorMessage(const INGconn *dbconn)
830 EXEC SQL BEGIN DECLARE SECTION;
833 EXEC SQL END DECLARE SECTION;
835 if (dbconn != NULL) {
837 * Switch to the correct default session for this thread.
839 sess_id = dbconn->session_id;
840 EXEC SQL SET_SQL (SESSION = :sess_id);
842 EXEC SQL INQUIRE_INGRES (:errbuf = ERRORTEXT);
843 strncpy(dbconn->msg, errbuf, sizeof(dbconn->msg));
846 * Switch to no default session for this thread.
848 EXEC SQL SET_SQL (SESSION = NONE);