4 #include <stdio.h> /* debuggering only. */
6 /* where to (optionally) stash performance data */
7 char *host_perf_table
= NULL
;
8 char *service_perf_table
= NULL
;
9 int sql_table_crashed
= 0;
10 static long int commit_interval
, commit_queries
;
11 static time_t last_commit
;
12 unsigned long total_queries
= 0;
15 #define MERLIN_DBT_MYSQL 0
16 #define MERLIN_DBT_PGSQL 2
17 #define MERLIN_DBT_SQLITE 3
20 * File-scoped definition of the database settings we've tried
21 * (or succeeded) connecting with
32 int port
; /* signed int for compatibility with dbi_conn_set_option_numeric() (and similar)*/
34 db_wrap_result
* result
;
54 * Quotes a string and escapes all meta-characters inside the string.
55 * If src is NULL or !*src then 0 is returned and *dest is not modified.
56 * *dst must be free()'d by the caller.
58 void sql_quote(const char *src
, char **dst
)
62 if (!sql_is_connected(1)) {
66 assert(db
.conn
!= NULL
);
67 ret
= db
.conn
->api
->sql_quote(db
.conn
, src
, (src
?strlen(src
):0U), dst
);
74 * these two functions are only here to allow callers
75 * access to error reporting without having to expose
76 * the db-link to theh callers. It's also nifty as we
77 * want to remain database layer agnostic.
79 * The returned bytes are owned by the underlying DB driver and are
80 * not guaranteed to be valid after the next call into the db
81 * driver. It is up to the client to copy them, if needed, BEFORE
82 * making ANY other calls into the DB API.
84 int sql_error(const char **msg
)
89 *msg
= "no database connection";
90 return DB_WRAP_E_UNKNOWN_ERROR
;
93 db
.conn
->api
->error_info(db
.conn
, msg
, NULL
, &dbrc
);
98 * Convenience form of sql_error() which returns the error
99 * string directly, or returns an unspecified string if
100 * no connection is established.
102 const char *sql_error_msg(void)
104 const char *msg
= NULL
;
109 void sql_free_result(void)
112 db
.result
->api
->finalize(db
.result
);
117 db_wrap_result
* sql_get_result(void)
122 void sql_try_commit(int query
)
125 time_t now
= time(NULL
);
127 if (!db
.conn
|| !use_database
|| !db
.conn
->api
->commit
)
135 (commit_interval
&& last_commit
+ commit_interval
<= now
) ||
136 (commit_queries
&& queries
>= commit_queries
)
140 ldebug("Committing %d queries", queries
);
142 * we ignore the return value here, as each db
143 * seems to return a different code on success
144 * and failure. Bleh...
146 (void)db
.conn
->api
->commit(db
.conn
);
148 total_queries
+= queries
;
153 static int run_query(char *query
, size_t len
, int rerunIGNORED
)
155 db_wrap_result
*res
= NULL
;
158 if (!db
.conn
&& sql_init() < 0) {
159 lerr("DB: No connection. Skipping query [%s]\n", query
);
163 assert(db
.conn
!= NULL
);
164 rc
= db
.conn
->api
->query_result(db
.conn
, query
, len
, &res
);
167 ldebug("MERLIN SQL: [%s]\n\tResult code: %d, result object @%p\n", query
, rc
, res
);
169 ldebug("Error code: %d\n", rc
);
181 assert(db
.result
== NULL
);
186 void sql_log_crashed(char *query
)
188 static time_t now
, last_log
= 0;
190 sql_table_crashed
= 1;
193 if (last_log
+ 30 > now
)
197 lerr("FATAL: One or more of your SQL tables have crashed. Please run 'mysqlrepair %s'",
199 lerr(" Query was: %s", query
);
202 int sql_vquery(const char *fmt
, va_list ap
)
215 * don't even bother trying to run the query if the database
216 * isn't online and we recently tried to connect to it
218 if (!sql_is_connected(1)) {
219 ldebug("DB: Not connected and re-init failed. Skipping query");
223 /* free any leftover result and run the new query */
226 len
= vasprintf(&query
, fmt
, ap
);
227 if (len
== -1 || !query
) {
228 lerr("sql_query: Failed to build query from format-string '%s'", fmt
);
232 if (run_query(query
, len
, 0) != 0) {
233 const char *error_msg
;
234 int db_error
= sql_error(&error_msg
);
238 * "table crashed" can get *very* spammy, so we put that in
239 * a logging function of its own
241 if (db_type
!= MERLIN_DBT_MYSQL
||
242 (db_error
!= 145 && db_error
!= 1194 && db_error
!= 1195))
244 lerr("Failed to run query [%s] due to error-code %d: %s",
245 query
, db_error
, error_msg
);
248 if (db_type
== MERLIN_DBT_MYSQL
) {
250 * if we failed because the connection has gone away, we try
251 * reconnecting once and rerunning the query before giving up.
252 * Otherwise we just ignore it and go on
255 case 1062: /* duplicate key */
256 case 1068: /* duplicate primary key */
257 case 1146: /* table missing */
258 case 2029: /* null pointer */
261 case 145: /* crashed table. ugh... */
262 case 1194: /* ER_CRASHED_ON_USAGE */
263 case 1195: /* ER_CRASHED_ON_REPAIR */
264 sql_log_crashed(query
);
266 * XXX: autofix by repairing the table and
267 * caching inbound queries while repair is running.
268 * We don't want to try reconnecting now though.
279 lwarn("Attempting to reconnect to database and re-run the query");
281 if (!run_query(query
, len
, 1))
282 lwarn("Successfully ran the previously failed query");
283 /* database backlog code goes here */
293 int sql_query(const char *fmt
, ...)
299 ret
= sql_vquery(fmt
, ap
);
305 int sql_table_exists(const char *tablename
)
307 db_wrap_result
*result
;
308 sql_query("SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '%s'", tablename
);
309 result
= sql_get_result();
313 return !(result
->api
->step(result
) == DB_WRAP_E_DONE
);
316 int sql_is_connected(int reconnect
)
318 int ret
= (db
.conn
&& db
.conn
->api
->is_connected(db
.conn
)) ? 1 : 0;
320 if (ret
|| !reconnect
)
323 return sql_init() == 0;
326 int sql_repair_table(const char *table
)
330 if (db_type
!= MERLIN_DBT_MYSQL
)
333 result
= sql_query("REPAIR TABLE %s", table
);
335 sql_table_crashed
= 0;
342 int result
, log_attempt
= 0;
343 static time_t last_logged
= 0;
344 db_wrap_conn_params connparam
= db_wrap_conn_params_empty
;
349 if (sql_is_connected(0)) {
350 ldebug("sql_init(): Already connected. Not reconnecting");
354 if (last_logged
+ 30 >= time(NULL
))
358 last_logged
= time(NULL
);
361 env
= getenv("MERLIN_LOG_SQL");
362 if (env
&& *env
!= 0) {
366 /* free any remaining result set */
369 db
.name
= sql_db_name();
370 db
.host
= sql_db_host();
371 db
.user
= sql_db_user();
372 db
.pass
= sql_db_pass();
373 db
.table
= sql_table_name();
374 db
.conn_str
= sql_db_conn_str();
379 if (!strcmp(db
.type
, "mysql") || !strcmp(db
.type
, "dbi:mysql")) {
380 db_type
= MERLIN_DBT_MYSQL
;
381 } else if (!strcmp(db
.type
, "psql") || !strcmp(db
.type
, "postgresql") || !strcmp(db
.type
, "pgsql")) {
382 db_type
= MERLIN_DBT_PGSQL
;
383 } else if (!strcmp(db
.type
, "sqlite")) {
384 db_type
= MERLIN_DBT_SQLITE
;
387 connparam
.host
= db
.host
;
388 connparam
.dbname
= db
.name
;
389 connparam
.username
= db
.user
;
390 connparam
.password
= db
.pass
;
391 if (db
.conn_str
&& *db
.conn_str
)
392 connparam
.conn_str
= db
.conn_str
;
394 connparam
.port
= db
.port
;
396 result
= db_wrap_driver_init(db
.type
, &connparam
, &db
.conn
);
399 if (db
.conn_str
&& *db
.conn_str
)
400 lerr("Failed to connect to db '%s' using connection string '%s' as user %s using driver %s",
401 db
.name
, db
.conn_str
, db
.user
, db
.type
);
403 lerr("Failed to connect to db '%s' at host '%s':'%d' as user %s using driver %s.",
404 db
.name
, db
.host
, db
.port
, db
.user
, db
.type
);
405 lerr("result: %d; errno: %d (%s)", result
, errno
, strerror(errno
));
410 result
= db
.conn
->api
->option_set(db
.conn
, "encoding", db
.encoding
? db
.encoding
: "latin1");
412 if (result
&& log_attempt
) {
413 lwarn("Warning: Failed to set encoding for the connection to db '%s' at host '%s':'%d' as user %s using driver %s.",
414 db
.name
, db
.host
, db
.port
, db
.user
, db
.type
);
417 result
= db
.conn
->api
->connect(db
.conn
);
420 const char *error_msg
;
421 sql_error(&error_msg
);
423 lerr("DB: Failed to connect to '%s' using connection string '%s' as %s:%s: %s",
424 db
.name
, db
.conn_str
, db
.user
, db
.pass
, error_msg
);
426 lerr("DB: Failed to connect to '%s' at '%s':'%d' as %s:%s: %s",
427 db
.name
, db
.host
, db
.port
, db
.user
, db
.pass
, error_msg
);
431 } else if (log_attempt
) {
432 ldebug("DB: Connected to db [%s] using driver [%s]",
437 * set auto-commit to ON if we have no commit parameters
438 * Drivers that doesn't support it or doesn't need it shouldn't
439 * have the "set_auto_commit()" function.
441 if (db
.conn
->api
->set_auto_commit
) {
442 int set
= !(commit_interval
| commit_queries
);
444 if (db
.conn
->api
->set_auto_commit(db
.conn
, set
) < 0) {
446 /* fake auto-commit with commit_queries = 1 */
450 lwarn("DB: set_auto_commit(%d) failed.", set
);
452 lwarn("DB: setting commit_interval = 1 as workaround");
455 } else if (log_attempt
) {
456 ldebug("DB: commit_queries: %ld; commit_interval: %ld",
457 commit_queries
, commit_interval
);
459 last_commit
= time(NULL
);
474 db
.conn
->api
->finalize(db
.conn
);
489 * Some nifty helper functions which still allow us to keep the
490 * db struct in file scope
493 const char *sql_db_name(void)
495 return db
.name
? db
.name
: "merlin";
498 const char *sql_db_user(void)
500 return db
.user
? db
.user
: "merlin";
503 const char *sql_db_pass(void)
505 return db
.pass
? db
.pass
: "merlin";
508 const char *sql_db_host(void)
510 return db
.host
? db
.host
: "localhost";
513 unsigned int sql_db_port(void)
518 const char *sql_db_type(void)
520 return db
.type
? db
.type
: "mysql";
523 const char *sql_table_name(void)
525 return db
.table
? db
.table
: "report_data";
528 const char *sql_db_conn_str(void)
530 return db
.conn_str
? db
.conn_str
: "";
535 * Config parameters from the "database" section end up here.
537 * The option "logsql" tells this API to log all SQL commands
538 * to stderr if value is not NULL and does not start with the
539 * character '0' (zero).
541 int sql_config(const char *key
, const char *value
)
546 value_cpy
= value
? strdup(value
) : NULL
;
548 if (!prefixcmp(key
, "name") || !prefixcmp(key
, "database"))
550 else if (!prefixcmp(key
, "logsql")) {
551 db
.logSQL
= strtobool(value
);
554 else if (!prefixcmp(key
, "user"))
556 else if (!prefixcmp(key
, "pass"))
558 else if (!prefixcmp(key
, "host"))
560 else if (!prefixcmp(key
, "type"))
562 else if (!prefixcmp(key
, "conn_str"))
563 db
.conn_str
= value_cpy
;
564 else if (!prefixcmp(key
, "port") && value
&& value
[0]) {
567 db
.port
= (unsigned int)strtoul(value
, &endp
, 0);
569 if (endp
== value
|| *endp
!= 0)
572 else if (!strcmp(key
, "host_perfdata_table"))
573 host_perf_table
= value_cpy
;
574 else if (!strcmp(key
, "service_perfdata_table"))
575 service_perf_table
= value_cpy
;
576 else if (!strcmp(key
, "perfdata_table"))
577 host_perf_table
= service_perf_table
= value_cpy
;
578 else if (!strcmp(key
, "commit_interval")) {
579 err
= grok_seconds(value
, &commit_interval
);
580 ldebug("DB: commit_interval set to %ld seconds", commit_interval
);
584 else if (!strcmp(key
, "commit_queries") && value_cpy
!= NULL
) {
586 commit_queries
= strtoul(value_cpy
, &endp
, 0);
587 ldebug("DB: commit_queries set to %ld queries", commit_queries
);
593 return -1; /* config error */