private:
PGconn *m_db_handle;
PGresult *m_result;
+ POOLMEM *m_buf; /* Buffer to manipulate queries */
public:
B_DB_POSTGRESQL(JCR *jcr, const char *db_driver, const char *db_name,
void db_start_transaction(JCR *jcr);
void db_end_transaction(JCR *jcr);
bool db_sql_query(const char *query, DB_RESULT_HANDLER *result_handler, void *ctx);
+ bool db_big_sql_query(const char *query, DB_RESULT_HANDLER *result_handler, void *ctx);
void sql_free_result(void);
SQL_ROW sql_fetch_row(void);
bool sql_query(const char *query, int flags=0);
virtual void db_start_transaction(JCR *jcr) = 0;
virtual void db_end_transaction(JCR *jcr) = 0;
virtual bool db_sql_query(const char *query, DB_RESULT_HANDLER *result_handler, void *ctx) = 0;
+
+ /* By default, we use db_sql_query */
+ virtual bool db_big_sql_query(const char *query,
+ DB_RESULT_HANDLER *result_handler, void *ctx) {
+ return db_sql_query(query, result_handler, ctx);
+ };
};
/* sql_query Query Flags */
#include "jcr.h"
#include "sql_cmds.h"
+/* Object used in db_list_xxx function */
+class LIST_CTX {
+public:
+ char line[256]; /* Used to print last dash line */
+ int32_t num_rows;
+
+ e_list_type type; /* Vertical/Horizontal */
+ DB_LIST_HANDLER *send; /* send data back */
+ bool once; /* Used to print header one time */
+ void *ctx; /* send() user argument */
+ B_DB *mdb;
+ JCR *jcr;
+
+ void empty() {
+ once = false;
+ line[0] = '\0';
+ }
+
+ void send_dashes() {
+ if (*line) {
+ send(ctx, line);
+ }
+ }
+
+ LIST_CTX(JCR *j, B_DB *m, DB_LIST_HANDLER *h, void *c, e_list_type t) {
+ line[0] = '\0';
+ once = false;
+ num_rows = 0;
+ type = t;
+ send = h;
+ ctx = c;
+ jcr = j;
+ mdb = m;
+ }
+};
+
/*
* Some functions exported by sql.c for use within the cats directory.
*/
-void list_result(JCR *jcr, B_DB *mdb, DB_LIST_HANDLER *send, void *ctx, e_list_type type);
+int list_result(void *vctx, int cols, char **row);
void list_dashes(B_DB *mdb, DB_LIST_HANDLER *send, void *ctx);
int get_sql_record_max(JCR *jcr, B_DB *mdb);
bool check_tables_version(JCR *jcr, B_DB *mdb);
path = get_pool_memory(PM_FNAME);
esc_name = get_pool_memory(PM_FNAME);
esc_path = get_pool_memory(PM_FNAME);
+ m_buf = get_pool_memory(PM_FNAME);
m_allow_transactions = mult_db_connections;
/*
}
sql_query("SET datestyle TO 'ISO, YMD'");
+ sql_query("SET cursor_tuple_fraction=1");
/*
* Tell PostgreSQL we are using standard conforming strings
free_pool_memory(path);
free_pool_memory(esc_name);
free_pool_memory(esc_path);
+ free_pool_memory(m_buf);
if (m_db_driver) {
free(m_db_driver);
}
db_unlock(this);
}
+
+/*
+ * Submit a general SQL command (cmd), and for each row returned,
+ * the result_handler is called with the ctx.
+ */
+bool B_DB_POSTGRESQL::db_big_sql_query(const char *query,
+ DB_RESULT_HANDLER *result_handler,
+ void *ctx)
+{
+ SQL_ROW row;
+ bool retval = false;
+ bool in_transaction = m_transaction;
+
+ Dmsg1(500, "db_sql_query starts with '%s'\n", query);
+
+ /* This code handles only SELECT queries */
+ if (strncasecmp(query, "SELECT", 6) != 0) {
+ return db_sql_query(query, result_handler, ctx);
+ }
+
+ db_lock(this);
+
+ if (!result_handler) { /* no need of big_query without handler */
+ goto bail_out;
+ }
+
+ if (!in_transaction) { /* CURSOR needs transaction */
+ sql_query("BEGIN");
+ }
+
+ Mmsg(m_buf, "DECLARE _bac_cursor CURSOR FOR %s", query);
+
+ if (!sql_query(m_buf)) {
+ Mmsg(errmsg, _("Query failed: %s: ERR=%s\n"), m_buf, sql_strerror());
+ Dmsg0(50, "db_sql_query failed\n");
+ goto bail_out;
+ }
+
+ do {
+ if (!sql_query("FETCH 100 FROM _bac_cursor")) {
+ goto bail_out;
+ }
+ while ((row = sql_fetch_row()) != NULL) {
+ Dmsg1(500, "Fetching %d rows\n", m_num_rows);
+ if (result_handler(ctx, m_num_fields, row))
+ break;
+ }
+ PQclear(m_result);
+ m_result = NULL;
+
+ } while (m_num_rows > 0); /* TODO: Can probably test against 100 */
+
+ sql_free_result();
+
+ if (!in_transaction) {
+ sql_query("COMMIT"); /* end transaction */
+ }
+
+ Dmsg0(500, "db_big_sql_query finished\n");
+ retval = true;
+
+bail_out:
+ db_unlock(this);
+ return retval;
+}
+
/*
* Submit a general SQL command (cmd), and for each row returned,
* the result_handler is called with the ctx.
Dmsg0(500, "sql_fetch_row start\n");
+ if (m_num_fields == 0) { /* No field, no row */
+ Dmsg0(500, "sql_fetch_row finishes returning NULL, no fields\n");
+ return NULL;
+ }
+
if (!m_rows || m_rows_size < m_num_fields) {
if (m_rows) {
Dmsg0(500, "sql_fetch_row freeing space\n");
HORZ_LIST,
VERT_LIST
};
+
void db_list_pool_records(JCR *jcr, B_DB *db, POOL_DBR *pr, DB_LIST_HANDLER sendit, void *ctx, e_list_type type);
void db_list_job_records(JCR *jcr, B_DB *db, JOB_DBR *jr, DB_LIST_HANDLER sendit, void *ctx, e_list_type type);
void db_list_job_totals(JCR *jcr, B_DB *db, JOB_DBR *jr, DB_LIST_HANDLER sendit, void *ctx);
send(ctx, "\n");
}
-/*
- * If full_list is set, we list vertically, otherwise, we
- * list on one line horizontally.
- */
-void
-list_result(JCR *jcr, B_DB *mdb, DB_LIST_HANDLER *send, void *ctx, e_list_type type)
+/* Small handler to print the last line of a list xxx command */
+static void last_line_handler(void *vctx, const char *str)
+{
+ LIST_CTX *ctx = (LIST_CTX *)vctx;
+ bstrncat(ctx->line, str, sizeof(ctx->line));
+}
+
+int list_result(void *vctx, int nb_col, char **row)
{
SQL_FIELD *field;
- SQL_ROW row;
int i, col_len, max_len = 0;
char buf[2000], ewc[30];
- Dmsg0(800, "list_result starts\n");
- if (sql_num_rows(mdb) == 0) {
- send(ctx, _("No results to list.\n"));
- return;
- }
+ LIST_CTX *pctx = (LIST_CTX *)vctx;
+ DB_LIST_HANDLER *send = pctx->send;
+ e_list_type type = pctx->type;
+ B_DB *mdb = pctx->mdb;
+ void *ctx = pctx->ctx;
+ JCR *jcr = pctx->jcr;
- Dmsg1(800, "list_result starts looking at %d fields\n", sql_num_fields(mdb));
- /* determine column display widths */
- sql_field_seek(mdb, 0);
- for (i = 0; i < sql_num_fields(mdb); i++) {
- Dmsg1(800, "list_result processing field %d\n", i);
- field = sql_fetch_field(mdb);
- if (!field) {
- break;
- }
- col_len = cstrlen(field->name);
- if (type == VERT_LIST) {
- if (col_len > max_len) {
- max_len = col_len;
+ if (!pctx->once) {
+ pctx->once = true;
+
+ Dmsg1(800, "list_result starts looking at %d fields\n", sql_num_fields(mdb));
+ /* determine column display widths */
+ sql_field_seek(mdb, 0);
+ for (i = 0; i < sql_num_fields(mdb); i++) {
+ Dmsg1(800, "list_result processing field %d\n", i);
+ field = sql_fetch_field(mdb);
+ if (!field) {
+ break;
}
- } else {
- if (sql_field_is_numeric(mdb, field->type) && (int)field->max_length > 0) { /* fixup for commas */
- field->max_length += (field->max_length - 1) / 3;
- }
- if (col_len < (int)field->max_length) {
- col_len = field->max_length;
- }
- if (col_len < 4 && !sql_field_is_not_null(mdb, field->flags)) {
- col_len = 4; /* 4 = length of the word "NULL" */
+ col_len = cstrlen(field->name);
+ if (type == VERT_LIST) {
+ if (col_len > max_len) {
+ max_len = col_len;
+ }
+ } else {
+ if (sql_field_is_numeric(mdb, field->type) && (int)field->max_length > 0) { /* fixup for commas */
+ field->max_length += (field->max_length - 1) / 3;
+ }
+ if (col_len < (int)field->max_length) {
+ col_len = field->max_length;
+ }
+ if (col_len < 4 && !sql_field_is_not_null(mdb, field->flags)) {
+ col_len = 4; /* 4 = length of the word "NULL" */
+ }
+ field->max_length = col_len; /* reset column info */
}
- field->max_length = col_len; /* reset column info */
}
- }
- Dmsg0(800, "list_result finished first loop\n");
- if (type == VERT_LIST) {
- goto vertical_list;
- }
+ pctx->num_rows++;
- Dmsg1(800, "list_result starts second loop looking at %d fields\n", sql_num_fields(mdb));
- list_dashes(mdb, send, ctx);
- send(ctx, "|");
- sql_field_seek(mdb, 0);
- for (i = 0; i < sql_num_fields(mdb); i++) {
- Dmsg1(800, "list_result looking at field %d\n", i);
- field = sql_fetch_field(mdb);
- if (!field) {
- break;
+ Dmsg0(800, "list_result finished first loop\n");
+ if (type == VERT_LIST) {
+ goto vertical_list;
}
- max_len = max_length(field->max_length);
- bsnprintf(buf, sizeof(buf), " %-*s |", max_len, field->name);
- send(ctx, buf);
- }
- send(ctx, "\n");
- list_dashes(mdb, send, ctx);
- Dmsg1(800, "list_result starts third loop looking at %d fields\n", sql_num_fields(mdb));
- while ((row = sql_fetch_row(mdb)) != NULL) {
- sql_field_seek(mdb, 0);
+ Dmsg1(800, "list_result starts second loop looking at %d fields\n",
+ sql_num_fields(mdb));
+
+ /* Keep the result to display the same line at the end of the table */
+ list_dashes(mdb, last_line_handler, pctx);
+ send(ctx, pctx->line);
+
send(ctx, "|");
+ sql_field_seek(mdb, 0);
for (i = 0; i < sql_num_fields(mdb); i++) {
+ Dmsg1(800, "list_result looking at field %d\n", i);
field = sql_fetch_field(mdb);
if (!field) {
break;
}
max_len = max_length(field->max_length);
- if (row[i] == NULL) {
- bsnprintf(buf, sizeof(buf), " %-*s |", max_len, "NULL");
- } else if (sql_field_is_numeric(mdb, field->type) && !jcr->gui && is_an_integer(row[i])) {
- bsnprintf(buf, sizeof(buf), " %*s |", max_len,
- add_commas(row[i], ewc));
- } else {
- bsnprintf(buf, sizeof(buf), " %-*s |", max_len, row[i]);
- }
+ bsnprintf(buf, sizeof(buf), " %-*s |", max_len, field->name);
send(ctx, buf);
}
send(ctx, "\n");
+ list_dashes(mdb, send, ctx);
+ }
+
+ Dmsg1(800, "list_result starts third loop looking at %d fields\n",
+ sql_num_fields(mdb));
+
+ sql_field_seek(mdb, 0);
+ send(ctx, "|");
+ for (i = 0; i < sql_num_fields(mdb); i++) {
+ field = sql_fetch_field(mdb);
+ if (!field) {
+ break;
+ }
+ max_len = max_length(field->max_length);
+ if (row[i] == NULL) {
+ bsnprintf(buf, sizeof(buf), " %-*s |", max_len, "NULL");
+ } else if (sql_field_is_numeric(mdb, field->type) && !jcr->gui && is_an_integer(row[i])) {
+ bsnprintf(buf, sizeof(buf), " %*s |", max_len,
+ add_commas(row[i], ewc));
+ } else {
+ bsnprintf(buf, sizeof(buf), " %-*s |", max_len, row[i]);
+ }
+ send(ctx, buf);
}
- list_dashes(mdb, send, ctx);
- return;
+ send(ctx, "\n");
+ return 0;
vertical_list:
Dmsg1(800, "list_result starts vertical list at %d fields\n", sql_num_fields(mdb));
- while ((row = sql_fetch_row(mdb)) != NULL) {
- sql_field_seek(mdb, 0);
- for (i = 0; i < sql_num_fields(mdb); i++) {
- field = sql_fetch_field(mdb);
- if (!field) {
- break;
- }
- if (row[i] == NULL) {
- bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, "NULL");
- } else if (sql_field_is_numeric(mdb, field->type) && !jcr->gui && is_an_integer(row[i])) {
- bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name,
- add_commas(row[i], ewc));
- } else {
- bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, row[i]);
- }
- send(ctx, buf);
+
+ sql_field_seek(mdb, 0);
+ for (i = 0; i < sql_num_fields(mdb); i++) {
+ field = sql_fetch_field(mdb);
+ if (!field) {
+ break;
}
- send(ctx, "\n");
+ if (row[i] == NULL) {
+ bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, "NULL");
+ } else if (sql_field_is_numeric(mdb, field->type) && !jcr->gui && is_an_integer(row[i])) {
+ bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name,
+ add_commas(row[i], ewc));
+ } else {
+ bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, row[i]);
+ }
+ send(ctx, buf);
}
- return;
+ send(ctx, "\n");
+ return 0;
}
/*
Dmsg1(100, "q=%s\n", buf.c_str());
- return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
+ return db_big_sql_query(mdb, buf.c_str(), result_handler, ctx);
}
/**
return mdb->db_sql_query(query, result_handler, ctx);
}
+bool db_big_sql_query(B_DB *mdb, const char *query, DB_RESULT_HANDLER *result_handler, void *ctx)
+{
+ return mdb->db_big_sql_query(query, result_handler, ctx);
+}
+
void sql_free_result(B_DB *mdb)
{
((B_DB_PRIV *)mdb)->sql_free_result();
void db_end_transaction(JCR *jcr, B_DB *mdb);
bool db_sql_query(B_DB *mdb, const char *query, int flags=0);
bool db_sql_query(B_DB *mdb, const char *query, DB_RESULT_HANDLER *result_handler, void *ctx);
+bool db_big_sql_query(B_DB *mdb, const char *query, DB_RESULT_HANDLER *result_handler, void *ctx);
#ifdef _BDB_PRIV_INTERFACE_
void sql_free_result(B_DB *mdb);
int db_list_sql_query(JCR *jcr, B_DB *mdb, const char *query, DB_LIST_HANDLER *sendit,
void *ctx, int verbose, e_list_type type)
{
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, type);
+
db_lock(mdb);
- if (!sql_query(mdb, query, QF_STORE_RESULT)) {
+
+ if (!db_big_sql_query(mdb, query, list_result, &lctx)) {
Mmsg(mdb->errmsg, _("Query failed: %s\n"), sql_strerror(mdb));
if (verbose) {
sendit(ctx, mdb->errmsg);
return 0;
}
- list_result(jcr, mdb, sendit, ctx, type);
+ lctx.send_dashes();
+
sql_free_result(mdb);
db_unlock(mdb);
return 1;
db_list_pool_records(JCR *jcr, B_DB *mdb, POOL_DBR *pdbr,
DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
{
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, type);
+
db_lock(mdb);
if (type == VERT_LIST) {
if (pdbr->Name[0] != 0) {
}
}
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+ if (!db_big_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
db_unlock(mdb);
return;
}
- list_result(jcr, mdb, sendit, ctx, type);
+ lctx.send_dashes();
sql_free_result(mdb);
db_unlock(mdb);
void
db_list_client_records(JCR *jcr, B_DB *mdb, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
{
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, type);
+
db_lock(mdb);
if (type == VERT_LIST) {
Mmsg(mdb->cmd, "SELECT ClientId,Name,Uname,AutoPrune,FileRetention,"
"FROM Client ORDER BY ClientId");
}
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+ if (!db_big_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
db_unlock(mdb);
return;
}
-
- list_result(jcr, mdb, sendit, ctx, type);
+ lctx.send_dashes();
sql_free_result(mdb);
db_unlock(mdb);
DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
{
char ed1[50];
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, type);
+
db_lock(mdb);
if (type == VERT_LIST) {
if (mdbr->VolumeName[0] != 0) {
}
}
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+ if (!db_big_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
db_unlock(mdb);
return;
}
- list_result(jcr, mdb, sendit, ctx, type);
+ lctx.send_dashes();
sql_free_result(mdb);
db_unlock(mdb);
DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
{
char ed1[50];
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, type);
+
db_lock(mdb);
if (type == VERT_LIST) {
if (JobId > 0) { /* do by JobId */
"FROM JobMedia,Media WHERE Media.MediaId=JobMedia.MediaId");
}
}
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+
+ if (!db_big_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
db_unlock(mdb);
return;
}
- list_result(jcr, mdb, sendit, ctx, type);
+ lctx.send_dashes();
sql_free_result(mdb);
db_unlock(mdb);
void db_list_copies_records(JCR *jcr, B_DB *mdb, uint32_t limit, char *JobIds,
DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
{
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, type);
POOL_MEM str_limit(PM_MESSAGE);
POOL_MEM str_jobids(PM_MESSAGE);
"WHERE Job.Type = '%c' %s ORDER BY Job.PriorJobId DESC %s",
(char) JT_JOB_COPY, str_jobids.c_str(), str_limit.c_str());
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
- goto bail_out;
+ if (JobIds && JobIds[0]) {
+ sendit(ctx, _("These JobIds have copies as follows:\n"));
+ } else {
+ sendit(ctx, _("The catalog contains copies as follows:\n"));
}
- if (sql_num_rows(mdb)) {
- if (JobIds && JobIds[0]) {
- sendit(ctx, _("These JobIds have copies as follows:\n"));
- } else {
- sendit(ctx, _("The catalog contains copies as follows:\n"));
- }
-
- list_result(jcr, mdb, sendit, ctx, type);
+ if (!db_big_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
+ goto bail_out;
}
+ lctx.send_dashes();
+
sql_free_result(mdb);
bail_out:
DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
{
char ed1[50];
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, type);
if (JobId <= 0) {
return;
Mmsg(mdb->cmd, "SELECT LogText FROM Log "
"WHERE Log.JobId=%s", edit_int64(JobId, ed1));
}
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+
+ if (!db_big_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
goto bail_out;
}
- list_result(jcr, mdb, sendit, ctx, type);
+ lctx.send_dashes();
sql_free_result(mdb);
{
char ed1[50];
char limit[100];
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, type);
+
db_lock(mdb);
if (jr->limit > 0) {
snprintf(limit, sizeof(limit), " LIMIT %d", jr->limit);
"FROM Job ORDER BY StartTime,JobId ASC%s", limit);
}
}
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+
+ if (!db_big_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
db_unlock(mdb);
return;
}
- list_result(jcr, mdb, sendit, ctx, type);
+ lctx.send_dashes();
sql_free_result(mdb);
db_unlock(mdb);
void
db_list_job_totals(JCR *jcr, B_DB *mdb, JOB_DBR *jr, DB_LIST_HANDLER *sendit, void *ctx)
{
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, HORZ_LIST);
+
db_lock(mdb);
/* List by Job */
Mmsg(mdb->cmd, "SELECT count(*) AS Jobs,sum(JobFiles) "
"AS Files,sum(JobBytes) AS Bytes,Name AS Job FROM Job GROUP BY Name");
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+ if (!db_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
db_unlock(mdb);
return;
}
-
- list_result(jcr, mdb, sendit, ctx, HORZ_LIST);
+ lctx.send_dashes();
sql_free_result(mdb);
Mmsg(mdb->cmd, "SELECT count(*) AS Jobs,sum(JobFiles) "
"AS Files,sum(JobBytes) As Bytes FROM Job");
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+ lctx.empty();
+ if (!db_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
db_unlock(mdb);
return;
}
- list_result(jcr, mdb, sendit, ctx, HORZ_LIST);
+ lctx.send_dashes();
sql_free_result(mdb);
db_unlock(mdb);
void
db_list_files_for_job(JCR *jcr, B_DB *mdb, JobId_t jobid, DB_LIST_HANDLER *sendit, void *ctx)
{
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, HORZ_LIST);
char ed1[50];
+
db_lock(mdb);
/*
edit_int64(jobid, ed1), ed1);
}
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+ if (!db_big_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
db_unlock(mdb);
return;
}
-
- list_result(jcr, mdb, sendit, ctx, HORZ_LIST);
+
+ lctx.send_dashes();
sql_free_result(mdb);
db_unlock(mdb);
void
db_list_base_files_for_job(JCR *jcr, B_DB *mdb, JobId_t jobid, DB_LIST_HANDLER *sendit, void *ctx)
{
+ LIST_CTX lctx(jcr, mdb, sendit, ctx, HORZ_LIST);
char ed1[50];
+
db_lock(mdb);
/*
edit_int64(jobid, ed1));
}
- if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+ if (!db_big_sql_query(mdb, mdb->cmd, list_result, &lctx)) {
db_unlock(mdb);
return;
}
- list_result(jcr, mdb, sendit, ctx, HORZ_LIST);
+ lctx.send_dashes();
sql_free_result(mdb);
db_unlock(mdb);