blob: 80a4d8229f7dc76b97ebcb310c62a5e02e25949c [file]
#include "die.h"
#include "fmt.h"
#include "scan.h"
#include "strerr.h"
#include "sub_sql.h"
#include <sys/types.h>
#include <libpq-fe.h>
#include <stdlib.h>
#include <string.h>
static stralloc line = {0};
struct _result
{
PGresult *res;
int row;
int nrows;
};
static void dummyNoticeProcessor(void *arg, const char *message)
{
(void)arg;
(void)message;
}
/* open connection to the SQL server, if it isn't already open. */
static const char *_opensub(struct subdbinfo *info)
{
char strnum[FMT_ULONG];
if (info->conn == 0) {
/* Make connection to database */
strnum[fmt_ulong(strnum,info->port)] = 0;
info->conn = PQsetdbLogin(info->host,info->port?strnum:"",NULL,NULL,
info->db,info->user,info->pw);
/* Check to see that the backend connection was successfully made */
if (PQstatus((PGconn*)info->conn) == CONNECTION_BAD)
return PQerrorMessage((PGconn*)info->conn);
/* Suppress output of notices. */
PQsetNoticeProcessor((PGconn*)info->conn, dummyNoticeProcessor, NULL);
}
return (char *) 0;
}
/* close connection to SQL server, if open */
static void _closesub(struct subdbinfo *info)
{
if ((PGconn*)info->conn)
PQfinish((PGconn*)info->conn);
info->conn = 0; /* Destroy pointer */
}
static void die_sqlerror(struct subdbinfo *info)
{
strerr_die2x(111,FATAL,PQerrorMessage((PGconn*)info->conn));
}
static void die_sqlresulterror(PGresult *result)
{
strerr_die2x(111,FATAL,PQresultErrorMessage(result));
}
PGresult *_execute(struct subdbinfo *info,
struct stralloc *q,
unsigned int nparams,
struct stralloc *params)
{
PGresult *result;
const char *values[nparams];
int lengths[nparams];
unsigned int i;
if (!stralloc_0(q)) die_nomem();
for (i = 0; i < nparams; ++i) {
if (!stralloc_0(&params[i])) die_nomem();
values[i] = params[i].s;
lengths[i] = params[i].len - 1;
}
result = PQexecParams((PGconn*)info->conn,q->s,nparams,0,values,lengths,0,0);
if (result == 0)
die_sqlerror(info);
return result;
}
int sql_exec(struct subdbinfo *info,
struct stralloc *q,
unsigned int nparams,
struct stralloc *params)
{
PGresult *result;
unsigned long rows;
const char *err;
result = _execute(info,q,nparams,params);
switch (PQresultStatus(result)) {
case PGRES_COMMAND_OK:
err = PQcmdTuples(result);
(void)scan_ulong(err,&rows);
break;
default:
/* This is ugly, but I can't find another good way of doing this */
err = PQresultErrorMessage(result);
if (strstr(err, "duplicate") != 0)
rows = 0;
else
die_sqlresulterror(result);
}
PQclear(result);
return rows;
}
void *sql_select(struct subdbinfo *info,
struct stralloc *q,
unsigned int nparams,
struct stralloc *params)
{
PGresult *result;
struct _result *ret;
if ((ret = malloc(sizeof *ret)) == 0) die_nomem();
result = _execute(info,q,nparams,params);
if (PQresultStatus(result) != PGRES_TUPLES_OK)
die_sqlresulterror(result);
ret->res = result;
ret->row = 0;
ret->nrows = PQntuples(result);
return ret;
}
int sql_fetch_row(struct subdbinfo *info,
void *result,
unsigned int ncolumns,
struct stralloc *columns)
{
struct _result *r = result;
unsigned int i;
if (r->row >= r->nrows)
return 0;
for (i = 0; i < ncolumns; ++i) {
if (!stralloc_copys(&columns[i],PQgetvalue(r->res,r->row,i)))
die_nomem();
}
++r->row;
return 1;
(void)info;
}
extern void sql_free_result(struct subdbinfo *info,
void *result)
{
struct _result *r = result;
PQclear(r->res);
free(r);
(void)info;
}
int sql_table_exists(struct subdbinfo *info,
const char *name)
{
PGresult *result;
/* This is a very crude cross-version portable kludge to test if a
* table exists by testing if a select from the table succeeds. */
if (!stralloc_copys(&line,"SELECT 0 FROM ")) return -1;
if (!stralloc_cats(&line,name)) return -1;
if (!stralloc_cats(&line," LIMIT 1")) return -1;
if (!stralloc_0(&line)) return -1;
result = PQexec((PGconn*)info->conn,line.s);
if (result == NULL)
return -1;
if (PQresultStatus(result) == PGRES_TUPLES_OK) {
PQclear(result);
return 1;
}
PQclear(result);
return 0;
}
const char *sql_create_table(struct subdbinfo *info,
const char *definition)
{
PGresult *result;
result = PQexec((PGconn*)info->conn,definition);
if (result == NULL)
return PQerrorMessage((PGconn*)info->conn);
if (PQresultStatus(result) != PGRES_COMMAND_OK)
return PQresultErrorMessage(result);
PQclear(result);
return 0;
}
/* Address table */
/* Need varchar. Domain = 3 chars => fixed length, as opposed to varchar
* Always select on domain and hash, so that one index should do primary
* key(address) is very inefficient for MySQL. MySQL tables do not need
* a primary key. Other RDBMS require one. For the log tables, just add
* an INT AUTO_INCREMENT. For the address table, do that or use address
* as a primary key. */
const char sql_sub_table_defn[] =
" hash INT4 NOT NULL,"
" address VARCHAR(255) NOT NULL PRIMARY KEY";
/* Subscription log table. No addr idx to make insertion fast, since
* that is almost the only thing we do with this table */
const char sql_slog_table_defn[] =
" tai TIMESTAMP DEFAULT now(),"
" address VARCHAR(255) NOT NULL,"
" fromline VARCHAR(255) NOT NULL,"
" edir CHAR NOT NULL,"
" etype CHAR NOT NULL";
/* main list inserts a cookie here. Sublists check it */
const char sql_cookie_table_defn[] =
" msgnum INT4 NOT NULL PRIMARY KEY,"
" tai TIMESTAMP NOT NULL DEFAULT now(),"
" cookie CHAR(20) NOT NULL,"
" chunk INT4 NOT NULL DEFAULT 0,"
" bodysize INT4 NOT NULL DEFAULT 0";
/* main and sublist log here when the message is done done=0 for
* arrived, done=4 for sent, 5 for receit. tai reflects last change */
const char sql_mlog_table_defn[] =
"msgnum INT4 NOT NULL,"
"listno INT4 NOT NULL,"
"tai TIMESTAMP DEFAULT now(),"
"subs INT4 NOT NULL DEFAULT 0,"
"done INT4 NOT NULL DEFAULT 0,"
"PRIMARY KEY (listno,msgnum,done)";
/* Definition of WHERE clauses for checktag */
const char sql_checktag_listno_where_defn[] = "listno=$1 AND msgnum=$2 AND done > 3";
const char sql_checktag_msgnum_where_defn[] = "msgnum=$1 AND cookie=$2";
/* Definition of WHERE clause for selecting addresses in issub */
const char sql_issub_where_defn[] = "address ~* ('^' || $1 || '$')";
/* Definition of VALUES clause for insert in logmsg */
const char sql_logmsg_values_defn[] = "($1,$2,$3,$4)";
/* Definition of WHERE clause for selecting addresses in putsubs */
const char sql_putsubs_where_defn[] = "hash BETWEEN $1 AND $2";
/* Definition of clauses for searchlog query */
const char sql_searchlog_select_defn[] = "extract(epoch from tai),address||' '||edir||etype||' '||fromline";
const char sql_searchlog_where_defn[] = "fromline LIKE concat('%',$1,'%') OR address LIKE concat('%',$1,'%')";
/* Definition of clauses for subscribe queries */
const char sql_subscribe_select_where_defn[] = "address=$1";
const char sql_subscribe_list_values_defn[] = "($1,$2)";
const char sql_subscribe_delete1_where_defn[] = "address ~* ('^' || $1 || '$') and hash BETWEEN 0 AND 52";
const char sql_subscribe_delete2_where_defn[] = "address ~* ('^' || $1 || '$') and hash=$2";
const char sql_subscribe_slog_values_defn[] = "($1,$2,$3,$4)";
/* Definition of VALUES clause for insert in tagmsg */
const char sql_tagmsg_values_defn[] = "($1,NOW(),$2,$3,$4)";
const char *sql_drop_table(struct subdbinfo *info,
const char *name)
{
PGresult *result;
if (!stralloc_copys(&line,"DROP TABLE ")) die_nomem();
if (!stralloc_cats(&line,name)) die_nomem();
if (!stralloc_0(&line)) die_nomem();
result = PQexec((PGconn*)info->conn,line.s);
if (result == NULL)
return PQerrorMessage((PGconn*)info->conn);
if (PQresultStatus(result) != PGRES_COMMAND_OK)
return PQresultErrorMessage(result);
PQclear(result);
return 0;
}
struct sub_plugin sub_plugin = {
SUB_PLUGIN_VERSION,
sub_sql_checktag,
_closesub,
sub_sql_issub,
sub_sql_logmsg,
sub_sql_mktab,
_opensub,
sub_sql_putsubs,
sub_sql_rmtab,
sub_sql_searchlog,
sub_sql_subscribe,
sub_sql_tagmsg,
};