summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarko Kreen2010-05-06 09:37:25 +0000
committerMarko Kreen2010-05-07 10:47:40 +0000
commit6cf0275e9efb8316ca2737bee2bc643364b5ccb8 (patch)
tree9de8e26da993cbfafc5c2fede09773b087fb7500
parent857d61029693d863e0aadbe76b9d7e3d94f84bc7 (diff)
pgq/triggers: Custom SQL expressions for field values
Move fields around for caching the trigger arguments and cached plans for override queries.
-rw-r--r--sql/pgq/expected/logutriga.out18
-rw-r--r--sql/pgq/expected/sqltriga.out18
-rw-r--r--sql/pgq/sql/logutriga.sql14
-rw-r--r--sql/pgq/sql/sqltriga.sql13
-rw-r--r--sql/pgq/triggers/Makefile3
-rw-r--r--sql/pgq/triggers/common.c290
-rw-r--r--sql/pgq/triggers/common.h71
-rw-r--r--sql/pgq/triggers/logtriga.c8
-rw-r--r--sql/pgq/triggers/logutriga.c23
-rw-r--r--sql/pgq/triggers/makesql.c33
-rw-r--r--sql/pgq/triggers/parsesql.c221
-rw-r--r--sql/pgq/triggers/parsesql.h12
-rw-r--r--sql/pgq/triggers/pgq_triggers.sql.in4
-rw-r--r--sql/pgq/triggers/qbuilder.c144
-rw-r--r--sql/pgq/triggers/qbuilder.h38
-rw-r--r--sql/pgq/triggers/sqltriga.c10
16 files changed, 809 insertions, 111 deletions
diff --git a/sql/pgq/expected/logutriga.out b/sql/pgq/expected/logutriga.out
index 74245e11..bab7c477 100644
--- a/sql/pgq/expected/logutriga.out
+++ b/sql/pgq/expected/logutriga.out
@@ -84,3 +84,21 @@ CONTEXT: SQL statement "select pgq.insert_event($1, $2, $3, $4, $5, $6, $7)"
delete from custom_fields2;
NOTICE: insert_event(que3, my_type, dat1=foo&dat2=2&dat3=bat, e1)
CONTEXT: SQL statement "select pgq.insert_event($1, $2, $3, $4, $5, $6, $7)"
+-- test custom expression
+create table custom_expr2 (
+ dat1 text not null primary key,
+ dat2 int2 not null,
+ dat3 text
+);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "custom_expr2_pkey" for table "custom_expr2"
+create trigger customex2_triga after insert or update or delete on custom_expr2
+for each row execute procedure pgq.logutriga('que3', 'ev_extra1=''test='' || dat1', 'ev_type=dat3');
+insert into custom_expr2 values ('foo', '2');
+NOTICE: insert_event(que3, <NULL>, dat1=foo&dat2=2&dat3, test=foo)
+CONTEXT: SQL statement "select pgq.insert_event($1, $2, $3, $4, $5, $6, $7)"
+update custom_expr2 set dat3 = 'bat';
+NOTICE: insert_event(que3, bat, dat1=foo&dat2=2&dat3=bat, test=foo)
+CONTEXT: SQL statement "select pgq.insert_event($1, $2, $3, $4, $5, $6, $7)"
+delete from custom_expr2;
+NOTICE: insert_event(que3, bat, dat1=foo&dat2=2&dat3=bat, test=foo)
+CONTEXT: SQL statement "select pgq.insert_event($1, $2, $3, $4, $5, $6, $7)"
diff --git a/sql/pgq/expected/sqltriga.out b/sql/pgq/expected/sqltriga.out
index 6f225867..139ab1d4 100644
--- a/sql/pgq/expected/sqltriga.out
+++ b/sql/pgq/expected/sqltriga.out
@@ -123,3 +123,21 @@ CONTEXT: SQL statement "select pgq.insert_event($1, $2, $3, $4, $5, $6, $7)"
delete from custom_fields;
NOTICE: insert_event(que3, my_type, dat1='foo', e1)
CONTEXT: SQL statement "select pgq.insert_event($1, $2, $3, $4, $5, $6, $7)"
+-- test custom expression
+create table custom_expr (
+ dat1 text not null primary key,
+ dat2 int2 not null,
+ dat3 text
+);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "custom_expr_pkey" for table "custom_expr"
+create trigger customex_triga after insert or update or delete on custom_expr
+for each row execute procedure pgq.sqltriga('que3', 'ev_extra1=''test='' || dat1', 'ev_type=dat3');
+insert into custom_expr values ('foo', '2');
+NOTICE: insert_event(que3, <NULL>, (dat1,dat2,dat3) values ('foo','2',null), test=foo)
+CONTEXT: SQL statement "select pgq.insert_event($1, $2, $3, $4, $5, $6, $7)"
+update custom_expr set dat3 = 'bat';
+NOTICE: insert_event(que3, bat, dat3='bat' where dat1='foo', test=foo)
+CONTEXT: SQL statement "select pgq.insert_event($1, $2, $3, $4, $5, $6, $7)"
+delete from custom_expr;
+NOTICE: insert_event(que3, bat, dat1='foo', test=foo)
+CONTEXT: SQL statement "select pgq.insert_event($1, $2, $3, $4, $5, $6, $7)"
diff --git a/sql/pgq/sql/logutriga.sql b/sql/pgq/sql/logutriga.sql
index 38b61dc7..3dd335df 100644
--- a/sql/pgq/sql/logutriga.sql
+++ b/sql/pgq/sql/logutriga.sql
@@ -69,3 +69,17 @@ update custom_fields2 set dat3 = 'bat';
delete from custom_fields2;
+-- test custom expression
+create table custom_expr2 (
+ dat1 text not null primary key,
+ dat2 int2 not null,
+ dat3 text
+);
+create trigger customex2_triga after insert or update or delete on custom_expr2
+for each row execute procedure pgq.logutriga('que3', 'ev_extra1=''test='' || dat1', 'ev_type=dat3');
+
+insert into custom_expr2 values ('foo', '2');
+update custom_expr2 set dat3 = 'bat';
+delete from custom_expr2;
+
+
diff --git a/sql/pgq/sql/sqltriga.sql b/sql/pgq/sql/sqltriga.sql
index f23adce4..9e655b12 100644
--- a/sql/pgq/sql/sqltriga.sql
+++ b/sql/pgq/sql/sqltriga.sql
@@ -92,4 +92,17 @@ insert into custom_fields values ('foo', '2');
update custom_fields set dat3 = 'bat';
delete from custom_fields;
+-- test custom expression
+create table custom_expr (
+ dat1 text not null primary key,
+ dat2 int2 not null,
+ dat3 text
+);
+create trigger customex_triga after insert or update or delete on custom_expr
+for each row execute procedure pgq.sqltriga('que3', 'ev_extra1=''test='' || dat1', 'ev_type=dat3');
+
+insert into custom_expr values ('foo', '2');
+update custom_expr set dat3 = 'bat';
+delete from custom_expr;
+
diff --git a/sql/pgq/triggers/Makefile b/sql/pgq/triggers/Makefile
index 9d5746a4..3f5d467b 100644
--- a/sql/pgq/triggers/Makefile
+++ b/sql/pgq/triggers/Makefile
@@ -3,7 +3,8 @@ include ../../../config.mak
MODULE_big = pgq_triggers
SRCS = logtriga.c logutriga.c sqltriga.c \
- common.c makesql.c stringutil.c
+ common.c makesql.c stringutil.c \
+ parsesql.c qbuilder.c
OBJS = $(SRCS:.c=.o)
DATA_built = pgq_triggers.sql
diff --git a/sql/pgq/triggers/common.c b/sql/pgq/triggers/common.c
index 11339c7a..d01a7e8c 100644
--- a/sql/pgq/triggers/common.c
+++ b/sql/pgq/triggers/common.c
@@ -31,6 +31,7 @@
#include "common.h"
#include "stringutil.h"
+#include "qbuilder.h"
/*
* Module tag
@@ -42,6 +43,9 @@ PG_MODULE_MAGIC;
/* memcmp is ok on NameData fields */
#define is_magic_field(s) (memcmp(s, "_pgq_ev_", 8) == 0)
+static void make_query(struct PgqTriggerEvent *ev, int fld, const char *arg);
+static void override_fields(struct PgqTriggerEvent *ev);
+
/*
* primary key info
*/
@@ -100,8 +104,9 @@ void pgq_simple_insert(const char *queue_name, Datum ev_type, Datum ev_data,
elog(ERROR, "call of pgq.insert_event failed");
}
-static void fill_magic_columns(PgqTriggerEvent *ev, TriggerData *tg)
+static void fill_magic_columns(PgqTriggerEvent *ev)
{
+ TriggerData *tg = ev->tgdata;
int i;
char *col_name, *col_value;
StringInfo *dst = NULL;
@@ -121,17 +126,17 @@ static void fill_magic_columns(PgqTriggerEvent *ev, TriggerData *tg)
if (!is_magic_field(col_name))
continue;
if (strcmp(col_name, "_pgq_ev_type") == 0)
- dst = &ev->ev_type;
+ dst = &ev->field[EV_TYPE];
else if (strcmp(col_name, "_pgq_ev_data") == 0)
- dst = &ev->ev_data;
+ dst = &ev->field[EV_DATA];
else if (strcmp(col_name, "_pgq_ev_extra1") == 0)
- dst = &ev->ev_extra1;
+ dst = &ev->field[EV_EXTRA1];
else if (strcmp(col_name, "_pgq_ev_extra2") == 0)
- dst = &ev->ev_extra2;
+ dst = &ev->field[EV_EXTRA2];
else if (strcmp(col_name, "_pgq_ev_extra3") == 0)
- dst = &ev->ev_extra3;
+ dst = &ev->field[EV_EXTRA3];
else if (strcmp(col_name, "_pgq_ev_extra4") == 0)
- dst = &ev->ev_extra4;
+ dst = &ev->field[EV_EXTRA4];
else
elog(ERROR, "Unknown magic column: %s", col_name);
@@ -145,21 +150,23 @@ static void fill_magic_columns(PgqTriggerEvent *ev, TriggerData *tg)
}
}
-void pgq_insert_tg_event(PgqTriggerEvent *ev, TriggerData *tg)
+void pgq_insert_tg_event(PgqTriggerEvent *ev)
{
- if (ev->custom_fields)
- fill_magic_columns(ev, tg);
+ if (ev->tgargs->custom_fields)
+ fill_magic_columns(ev);
+
+ override_fields(ev);
pgq_simple_insert(ev->queue_name,
- pgq_finish_varbuf(ev->ev_type),
- pgq_finish_varbuf(ev->ev_data),
- pgq_finish_varbuf(ev->ev_extra1),
- pgq_finish_varbuf(ev->ev_extra2),
- pgq_finish_varbuf(ev->ev_extra3),
- pgq_finish_varbuf(ev->ev_extra4));
+ pgq_finish_varbuf(ev->field[EV_TYPE]),
+ pgq_finish_varbuf(ev->field[EV_DATA]),
+ pgq_finish_varbuf(ev->field[EV_EXTRA1]),
+ pgq_finish_varbuf(ev->field[EV_EXTRA2]),
+ pgq_finish_varbuf(ev->field[EV_EXTRA3]),
+ pgq_finish_varbuf(ev->field[EV_EXTRA4]));
}
-char *pgq_find_table_name(Relation rel)
+static char *find_table_name(Relation rel)
{
NameData tname = rel->rd_rel->relname;
Oid nsoid = rel->rd_rel->relnamespace;
@@ -216,6 +223,21 @@ static void init_cache(void)
tbl_cache_map = hash_create("pgq_triggers pkey cache", max_tables, &ctl, flags);
}
+static void clean_htab(void)
+{
+ HASH_SEQ_STATUS seq;
+ struct PgqTableInfo *entry;
+ struct PgqTriggerInfo *tg;
+ hash_seq_init(&seq, tbl_cache_map);
+ while (1) {
+ entry = hash_seq_search(&seq);
+ if (!entry)
+ break;
+ for (tg = entry->tg_cache; tg; tg = tg->next) {
+ }
+ }
+}
+
/*
* Prepare utility plans and plan cache.
*/
@@ -227,8 +249,10 @@ static void init_module(void)
if (tbl_cache_invalid) {
if (tbl_cache_map)
hash_destroy(tbl_cache_map);
- if (tbl_cache_ctx)
+ if (tbl_cache_ctx) {
+ clean_htab();
MemoryContextDelete(tbl_cache_ctx);
+ }
tbl_cache_map = NULL;
tbl_cache_ctx = NULL;
tbl_cache_invalid = false;
@@ -259,7 +283,7 @@ static void fill_tbl_info(Relation rel, struct PgqTableInfo *info)
{
StringInfo pkeys;
Datum values[1];
- const char *name = pgq_find_table_name(rel);
+ const char *name = find_table_name(rel);
TupleDesc desc;
HeapTuple row;
bool isnull;
@@ -295,10 +319,17 @@ static void fill_tbl_info(Relation rel, struct PgqTableInfo *info)
appendStringInfoString(pkeys, name);
}
info->pkey_list = MemoryContextStrdup(tbl_cache_ctx, pkeys->data);
+ info->tg_cache = NULL;
}
static void free_info(struct PgqTableInfo *info)
{
+ struct PgqTriggerInfo *tg, *tmp = info->tg_cache;
+ for (tg = info->tg_cache; tg; ) {
+ tmp = tg->next;
+ pfree(tg);
+ tg = tmp;
+ }
pfree(info->table_name);
pfree(info->pkey_attno);
pfree((void *)info->pkey_list);
@@ -323,7 +354,7 @@ static void relcache_reset_cb(Datum arg, Oid relid)
/*
* fetch table struct from cache.
*/
-struct PgqTableInfo *pgq_find_table_info(Relation rel)
+static struct PgqTableInfo *find_table_info(Relation rel)
{
struct PgqTableInfo *entry;
bool found = false;
@@ -340,35 +371,55 @@ struct PgqTableInfo *pgq_find_table_info(Relation rel)
return entry;
}
+static struct PgqTriggerInfo *find_trigger_info(struct PgqTableInfo *info, Oid tgoid, bool create)
+{
+ struct PgqTriggerInfo *tgargs = info->tg_cache;
+ for (tgargs = info->tg_cache; tgargs; tgargs = tgargs->next) {
+ if (tgargs->tgoid == tgoid)
+ return tgargs;
+ }
+ if (!create)
+ return NULL;
+ tgargs = MemoryContextAllocZero(tbl_cache_ctx, sizeof(*tgargs));
+ tgargs->tgoid = tgoid;
+ tgargs->next = info->tg_cache;
+ info->tg_cache = tgargs;
+ return tgargs;
+}
+
static void parse_newstyle_args(PgqTriggerEvent *ev, TriggerData *tg)
{
int i;
+
/*
* parse args
*/
- ev->skip = false;
- ev->queue_name = tg->tg_trigger->tgargs[0];
for (i = 1; i < tg->tg_trigger->tgnargs; i++) {
const char *arg = tg->tg_trigger->tgargs[i];
if (strcmp(arg, "SKIP") == 0)
- ev->skip = true;
+ ev->tgargs->skip = true;
else if (strncmp(arg, "ignore=", 7) == 0)
- ev->ignore_list = arg + 7;
+ ev->tgargs->ignore_list = MemoryContextStrdup(tbl_cache_ctx, arg + 7);
else if (strncmp(arg, "pkey=", 5) == 0)
- ev->pkey_list = arg + 5;
+ ev->tgargs->pkey_list = MemoryContextStrdup(tbl_cache_ctx, arg + 5);
else if (strcmp(arg, "backup") == 0)
- ev->backup = true;
+ ev->tgargs->backup = true;
+ else if (strncmp(arg, "ev_extra4=", 10) == 0)
+ make_query(ev, EV_EXTRA4, arg + 10);
+ else if (strncmp(arg, "ev_extra3=", 10) == 0)
+ make_query(ev, EV_EXTRA3, arg + 10);
+ else if (strncmp(arg, "ev_extra2=", 10) == 0)
+ make_query(ev, EV_EXTRA2, arg + 10);
+ else if (strncmp(arg, "ev_extra1=", 10) == 0)
+ make_query(ev, EV_EXTRA1, arg + 10);
+ else if (strncmp(arg, "ev_data=", 8) == 0)
+ make_query(ev, EV_DATA, arg + 8);
+ else if (strncmp(arg, "ev_type=", 8) == 0)
+ make_query(ev, EV_TYPE, arg + 8);
else
elog(ERROR, "bad param to pgq trigger");
}
- /*
- * Check if we have pkey
- */
- if (ev->op_type == 'U' || ev->op_type == 'D') {
- if (ev->pkey_list[0] == 0)
- elog(ERROR, "Update/Delete on table without pkey");
- }
}
static void parse_oldstyle_args(PgqTriggerEvent *ev, TriggerData *tg)
@@ -377,16 +428,13 @@ static void parse_oldstyle_args(PgqTriggerEvent *ev, TriggerData *tg)
int attcnt, i;
TupleDesc tupdesc = tg->tg_relation->rd_att;
- ev->skip = false;
if (tg->tg_trigger->tgnargs < 2 || tg->tg_trigger->tgnargs > 3)
elog(ERROR, "pgq.logtriga must be used with 2 or 3 args");
- ev->queue_name = tg->tg_trigger->tgargs[0];
ev->attkind = tg->tg_trigger->tgargs[1];
ev->attkind_len = strlen(ev->attkind);
if (tg->tg_trigger->tgnargs > 2)
ev->table_name = tg->tg_trigger->tgargs[2];
-
/*
* Count number of active columns
*/
@@ -438,39 +486,54 @@ void pgq_prepare_event(struct PgqTriggerEvent *ev, TriggerData *tg, bool newstyl
/*
* load table info
*/
- ev->info = pgq_find_table_info(tg->tg_relation);
+ ev->tgdata = tg;
+ ev->info = find_table_info(tg->tg_relation);
ev->table_name = ev->info->table_name;
ev->pkey_list = ev->info->pkey_list;
+ ev->queue_name = tg->tg_trigger->tgargs[0];
/*
- * parse args
+ * parse args, newstyle args are cached
*/
- if (newstyle)
- parse_newstyle_args(ev, tg);
- else
+ if (newstyle) {
+ ev->tgargs = find_trigger_info(ev->info, tg->tg_trigger->tgoid, false);
+ if (!ev->tgargs) {
+ ev->tgargs = find_trigger_info(ev->info, tg->tg_trigger->tgoid, true);
+ parse_newstyle_args(ev, tg);
+ }
+ if (ev->tgargs->pkey_list)
+ ev->pkey_list = ev->tgargs->pkey_list;
+ /* Check if we have pkey */
+ if (ev->op_type == 'U' || ev->op_type == 'D') {
+ if (ev->pkey_list[0] == 0)
+ elog(ERROR, "Update/Delete on table without pkey");
+ }
+ } else {
parse_oldstyle_args(ev, tg);
+ }
/*
* init data
*/
- ev->ev_type = pgq_init_varbuf();
- ev->ev_data = pgq_init_varbuf();
- ev->ev_extra1 = pgq_init_varbuf();
+ ev->field[EV_TYPE] = pgq_init_varbuf();
+ ev->field[EV_DATA] = pgq_init_varbuf();
+ ev->field[EV_EXTRA1] = pgq_init_varbuf();
/*
* Do the backup, if requested.
*/
- if (ev->backup) {
- ev->ev_extra2 = pgq_init_varbuf();
- pgq_urlenc_row(ev, tg, tg->tg_trigtuple, ev->ev_extra2);
+ if (ev->tgargs->backup) {
+ ev->field[EV_EXTRA2] = pgq_init_varbuf();
+ pgq_urlenc_row(ev, tg->tg_trigtuple, ev->field[EV_EXTRA2]);
}
}
/*
* Check if column should be skipped
*/
-bool pgqtriga_skip_col(PgqTriggerEvent *ev, TriggerData *tg, int i, int attkind_idx)
+bool pgqtriga_skip_col(PgqTriggerEvent *ev, int i, int attkind_idx)
{
+ TriggerData *tg = ev->tgdata;
TupleDesc tupdesc;
const char *name;
@@ -480,7 +543,7 @@ bool pgqtriga_skip_col(PgqTriggerEvent *ev, TriggerData *tg, int i, int attkind_
name = NameStr(tupdesc->attrs[i]->attname);
if (is_magic_field(name)) {
- ev->custom_fields = 1;
+ ev->tgargs->custom_fields = 1;
return true;
}
@@ -488,8 +551,8 @@ bool pgqtriga_skip_col(PgqTriggerEvent *ev, TriggerData *tg, int i, int attkind_
if (attkind_idx >= ev->attkind_len)
return true;
return ev->attkind[attkind_idx] == 'i';
- } else if (ev->ignore_list) {
- return pgq_strlist_contains(ev->ignore_list, name);
+ } else if (ev->tgargs->ignore_list) {
+ return pgq_strlist_contains(ev->tgargs->ignore_list, name);
}
return false;
}
@@ -497,8 +560,9 @@ bool pgqtriga_skip_col(PgqTriggerEvent *ev, TriggerData *tg, int i, int attkind_
/*
* Check if column is pkey.
*/
-bool pgqtriga_is_pkey(PgqTriggerEvent *ev, TriggerData *tg, int i, int attkind_idx)
+bool pgqtriga_is_pkey(PgqTriggerEvent *ev, int i, int attkind_idx)
{
+ TriggerData *tg = ev->tgdata;
TupleDesc tupdesc;
const char *name;
@@ -512,7 +576,7 @@ bool pgqtriga_is_pkey(PgqTriggerEvent *ev, TriggerData *tg, int i, int attkind_i
return false;
name = NameStr(tupdesc->attrs[i]->attname);
if (is_magic_field(name)) {
- ev->custom_fields = 1;
+ ev->tgargs->custom_fields = 1;
return false;
}
return pgq_strlist_contains(ev->pkey_list, name);
@@ -537,3 +601,123 @@ bool pgq_is_logging_disabled(void)
#endif
return false;
}
+
+/*
+ * Callbacks for queryfilter
+ */
+
+static int tg_name_lookup(void *arg, const char *name, int len)
+{
+ TriggerData *tg = arg;
+ TupleDesc desc = tg->tg_relation->rd_att;
+ char namebuf[NAMEDATALEN + 1];
+ int nr;
+
+ if (len >= sizeof(namebuf))
+ return -1;
+ memcpy(namebuf, name, len);
+ namebuf[len] = 0;
+
+ nr = SPI_fnumber(desc, namebuf);
+ if (nr > 0)
+ return nr;
+ return -1;
+}
+
+static Oid tg_type_lookup(void *arg, int spi_nr)
+{
+ TriggerData *tg = arg;
+ TupleDesc desc = tg->tg_relation->rd_att;
+
+ return SPI_gettypeid(desc, spi_nr);
+}
+
+static Datum tg_value_lookup(void *arg, int spi_nr, bool *isnull)
+{
+ TriggerData *tg = arg;
+ TupleDesc desc = tg->tg_relation->rd_att;
+ HeapTuple row;
+
+ if (TRIGGER_FIRED_BY_UPDATE(tg->tg_event))
+ row = tg->tg_newtuple;
+ else
+ row = tg->tg_trigtuple;
+
+ return SPI_getbinval(row, desc, spi_nr, isnull);
+}
+
+static const struct QueryBuilderOps tg_ops = {
+ tg_name_lookup,
+ tg_type_lookup,
+ tg_value_lookup,
+};
+
+/*
+ * Custom override queries for field values.
+ */
+
+static void make_query(struct PgqTriggerEvent *ev, int fld, const char *arg)
+{
+ struct TriggerData *tg = ev->tgdata;
+ struct PgqTriggerInfo *tgargs;
+ struct QueryBuilder *q;
+ Oid tgoid = tg->tg_trigger->tgoid;
+ const char *pfx = "select ";
+
+ /* make sure tgargs exists */
+ if (!ev->tgargs)
+ ev->tgargs = find_trigger_info(ev->info, tgoid, true);
+ tgargs = ev->tgargs;
+
+ if (tgargs->query[fld]) {
+ /* seems we already have prepared query */
+ if (tgargs->query[fld]->plan)
+ return;
+ /* query is broken, last prepare failed? */
+ qb_free(tgargs->query[fld]);
+ tgargs->query[fld] = NULL;
+ }
+
+ /* allocate query in right context */
+ q = qb_create(&tg_ops, tbl_cache_ctx);
+
+ /* attach immediately */
+ tgargs->query[fld] = q;
+
+ /* prepare the query */
+ qb_add_raw(q, pfx, strlen(pfx));
+ qb_add_parse(q, arg, tg);
+ qb_prepare(q, tg);
+}
+
+static void override_fields(struct PgqTriggerEvent *ev)
+{
+ TriggerData *tg = ev->tgdata;
+ int res, i;
+ char *val;
+
+ /* no overrides */
+ if (!ev->tgargs)
+ return;
+
+ for (i = 0; i < EV_NFIELDS; i++) {
+ if (!ev->tgargs->query[i])
+ continue;
+ res = qb_execute(ev->tgargs->query[i], tg);
+ if (res != SPI_OK_SELECT)
+ elog(ERROR, "Override query failed");
+ if (SPI_processed != 1)
+ elog(ERROR, "Expect 1 row from override query, got %d", SPI_processed);
+ val = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
+ if (ev->field[i]) {
+ pfree(ev->field[i]->data);
+ pfree(ev->field[i]);
+ ev->field[i] = NULL;
+ }
+ if (val) {
+ ev->field[i] = pgq_init_varbuf();
+ appendStringInfoString(ev->field[i], val);
+ }
+ }
+}
+
diff --git a/sql/pgq/triggers/common.h b/sql/pgq/triggers/common.h
index f3e79489..fac8f0d8 100644
--- a/sql/pgq/triggers/common.h
+++ b/sql/pgq/triggers/common.h
@@ -1,61 +1,90 @@
+enum PgqFields {
+ EV_TYPE = 0,
+ EV_DATA,
+ EV_EXTRA1,
+ EV_EXTRA2,
+ EV_EXTRA3,
+ EV_EXTRA4,
+ EV_NFIELDS
+};
/*
* Per-event temporary data.
*/
struct PgqTriggerEvent {
+ char op_type;
+
+ /* overridable fields */
+ // fixme: check proper usage
const char *table_name;
const char *queue_name;
- const char *ignore_list;
const char *pkey_list;
+ /* no cache for old-style args */
const char *attkind;
int attkind_len;
- char op_type;
+ /* cached per-table info */
+ struct PgqTableInfo *info;
+
+ /* cached per-trigger args */
+ struct PgqTriggerInfo *tgargs;
+
+ /* current event data */
+ TriggerData *tgdata;
+
+ /* result fields */
+ StringInfo field[EV_NFIELDS];
+};
+typedef struct PgqTriggerEvent PgqTriggerEvent;
+
+/*
+ * Per trigger cached info, stored under table cache,
+ * so that invalidate can drop it.
+ */
+struct PgqTriggerInfo {
+ struct PgqTriggerInfo *next;
+ Oid tgoid;
+
+ const char *ignore_list;
+ const char *pkey_list;
+
bool skip;
bool backup;
bool custom_fields;
- struct PgqTableInfo *info;
-
- StringInfo ev_type;
- StringInfo ev_data;
- StringInfo ev_extra1;
- StringInfo ev_extra2;
- StringInfo ev_extra3;
- StringInfo ev_extra4;
+ struct QueryBuilder *query[EV_NFIELDS];
};
-typedef struct PgqTriggerEvent PgqTriggerEvent;
/*
* Per-table cached info.
*
- * Can be shared between triggers on same table,
- * so nothing trigger-specific should be stored.
+ * Per-trigger info should be cached under tg_cache.
*/
struct PgqTableInfo {
- Oid oid; /* must be first, used by htab */
+ Oid reloid; /* must be first, used by htab */
int n_pkeys; /* number of pkeys */
const char *pkey_list; /* pk column name list */
int *pkey_attno; /* pk column positions */
char *table_name; /* schema-quelified table name */
bool invalid; /* set if the info was invalidated */
+
+ struct PgqTriggerInfo *tg_cache;
};
/* common.c */
-struct PgqTableInfo *pgq_find_table_info(Relation rel);
void pgq_prepare_event(struct PgqTriggerEvent *ev, TriggerData *tg, bool newstyle);
-char *pgq_find_table_name(Relation rel);
void pgq_simple_insert(const char *queue_name, Datum ev_type, Datum ev_data,
Datum ev_extra1, Datum ev_extra2, Datum ev_extra3, Datum ev_extra4);
-bool pgqtriga_skip_col(PgqTriggerEvent *ev, TriggerData *tg, int i, int attkind_idx);
-bool pgqtriga_is_pkey(PgqTriggerEvent *ev, TriggerData *tg, int i, int attkind_idx);
-void pgq_insert_tg_event(PgqTriggerEvent *ev, TriggerData *tg);
+bool pgqtriga_skip_col(PgqTriggerEvent *ev, int i, int attkind_idx);
+bool pgqtriga_is_pkey(PgqTriggerEvent *ev, int i, int attkind_idx);
+void pgq_insert_tg_event(PgqTriggerEvent *ev);
bool pgq_is_logging_disabled(void);
/* makesql.c */
-int pgqtriga_make_sql(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql);
+int pgqtriga_make_sql(PgqTriggerEvent *ev, StringInfo sql);
/* logutriga.c */
-void pgq_urlenc_row(PgqTriggerEvent *ev, TriggerData *tg, HeapTuple row, StringInfo buf);
+void pgq_urlenc_row(PgqTriggerEvent *ev, HeapTuple row, StringInfo buf);
+
diff --git a/sql/pgq/triggers/logtriga.c b/sql/pgq/triggers/logtriga.c
index 0a474198..eb810f84 100644
--- a/sql/pgq/triggers/logtriga.c
+++ b/sql/pgq/triggers/logtriga.c
@@ -66,14 +66,14 @@ Datum pgq_logtriga(PG_FUNCTION_ARGS)
pgq_prepare_event(&ev, tg, false);
- appendStringInfoChar(ev.ev_type, ev.op_type);
- appendStringInfoString(ev.ev_extra1, ev.info->table_name);
+ appendStringInfoChar(ev.field[EV_TYPE], ev.op_type);
+ appendStringInfoString(ev.field[EV_EXTRA1], ev.info->table_name);
/*
* create sql and insert if interesting
*/
- if (pgqtriga_make_sql(&ev, tg, ev.ev_data))
- pgq_insert_tg_event(&ev, tg);
+ if (pgqtriga_make_sql(&ev, ev.field[EV_DATA]))
+ pgq_insert_tg_event(&ev);
if (SPI_finish() < 0)
elog(ERROR, "SPI_finish failed");
diff --git a/sql/pgq/triggers/logutriga.c b/sql/pgq/triggers/logutriga.c
index b3d4cf50..1c105819 100644
--- a/sql/pgq/triggers/logutriga.c
+++ b/sql/pgq/triggers/logutriga.c
@@ -49,7 +49,7 @@ static int is_interesting_change(PgqTriggerEvent *ev, TriggerData *tg)
return 1;
/* if no columns are ignored, all events are interesting */
- if (ev->ignore_list == NULL)
+ if (ev->tgargs->ignore_list == NULL)
return 1;
for (i = 0; i < tupdesc->natts; i++) {
@@ -108,7 +108,7 @@ static int is_interesting_change(PgqTriggerEvent *ev, TriggerData *tg)
}
}
- if (pgqtriga_skip_col(ev, tg, i, attkind_idx)) {
+ if (pgqtriga_skip_col(ev, i, attkind_idx)) {
/* this change should be ignored */
ignore_count++;
continue;
@@ -126,8 +126,9 @@ static int is_interesting_change(PgqTriggerEvent *ev, TriggerData *tg)
return 1;
}
-void pgq_urlenc_row(PgqTriggerEvent *ev, TriggerData *tg, HeapTuple row, StringInfo buf)
+void pgq_urlenc_row(PgqTriggerEvent *ev, HeapTuple row, StringInfo buf)
{
+ TriggerData *tg = ev->tgdata;
TupleDesc tupdesc = tg->tg_relation->rd_att;
bool first = true;
int i;
@@ -141,7 +142,7 @@ void pgq_urlenc_row(PgqTriggerEvent *ev, TriggerData *tg, HeapTuple row, StringI
attkind_idx++;
- if (pgqtriga_skip_col(ev, tg, i, attkind_idx))
+ if (pgqtriga_skip_col(ev, i, attkind_idx))
continue;
if (first)
@@ -201,21 +202,21 @@ Datum pgq_logutriga(PG_FUNCTION_ARGS)
pgq_prepare_event(&ev, tg, true);
- appendStringInfoChar(ev.ev_type, ev.op_type);
- appendStringInfoChar(ev.ev_type, ':');
- appendStringInfoString(ev.ev_type, ev.pkey_list);
- appendStringInfoString(ev.ev_extra1, ev.info->table_name);
+ appendStringInfoChar(ev.field[EV_TYPE], ev.op_type);
+ appendStringInfoChar(ev.field[EV_TYPE], ':');
+ appendStringInfoString(ev.field[EV_TYPE], ev.pkey_list);
+ appendStringInfoString(ev.field[EV_EXTRA1], ev.info->table_name);
if (is_interesting_change(&ev, tg)) {
/*
* create type, data
*/
- pgq_urlenc_row(&ev, tg, row, ev.ev_data);
+ pgq_urlenc_row(&ev, row, ev.field[EV_DATA]);
/*
* Construct the parameter array and insert the log row.
*/
- pgq_insert_tg_event(&ev, tg);
+ pgq_insert_tg_event(&ev);
}
if (SPI_finish() < 0)
@@ -226,7 +227,7 @@ Datum pgq_logutriga(PG_FUNCTION_ARGS)
* before trigger skips event if NULL.
*/
skip_it:
- if (TRIGGER_FIRED_AFTER(tg->tg_event) || ev.skip)
+ if (TRIGGER_FIRED_AFTER(tg->tg_event) || ev.tgargs->skip)
return PointerGetDatum(NULL);
else
return PointerGetDatum(row);
diff --git a/sql/pgq/triggers/makesql.c b/sql/pgq/triggers/makesql.c
index 53f5ef86..de4ab565 100644
--- a/sql/pgq/triggers/makesql.c
+++ b/sql/pgq/triggers/makesql.c
@@ -52,8 +52,9 @@ static void append_normal_eq(StringInfo buf, const char *col_ident, const char *
appendStringInfoString(buf, "NULL");
}
-static void process_insert(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
+static void process_insert(PgqTriggerEvent *ev, StringInfo sql)
{
+ TriggerData *tg = ev->tgdata;
HeapTuple new_row = tg->tg_trigtuple;
TupleDesc tupdesc = tg->tg_relation->rd_att;
int i;
@@ -74,7 +75,7 @@ static void process_insert(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
/* Check if allowed by colstring */
attkind_idx++;
- if (pgqtriga_skip_col(ev, tg, i, attkind_idx))
+ if (pgqtriga_skip_col(ev, i, attkind_idx))
continue;
if (need_comma)
@@ -106,7 +107,7 @@ static void process_insert(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
/* Check if allowed by colstring */
attkind_idx++;
- if (pgqtriga_skip_col(ev, tg, i, attkind_idx))
+ if (pgqtriga_skip_col(ev, i, attkind_idx))
continue;
if (need_comma)
@@ -128,8 +129,9 @@ static void process_insert(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
appendStringInfoChar(sql, ')');
}
-static int process_update(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
+static int process_update(PgqTriggerEvent *ev, StringInfo sql)
{
+ TriggerData *tg = ev->tgdata;
HeapTuple old_row = tg->tg_trigtuple;
HeapTuple new_row = tg->tg_newtuple;
TupleDesc tupdesc = tg->tg_relation->rd_att;
@@ -204,7 +206,7 @@ static int process_update(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
}
}
- if (pgqtriga_skip_col(ev, tg, i, attkind_idx)) {
+ if (pgqtriga_skip_col(ev, i, attkind_idx)) {
/* this change should be ignored */
ignore_count++;
continue;
@@ -238,7 +240,7 @@ static int process_update(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
continue;
attkind_idx++;
- if (pgqtriga_is_pkey(ev, tg, i, attkind_idx))
+ if (pgqtriga_is_pkey(ev, i, attkind_idx))
break;
}
col_ident = SPI_fname(tupdesc, i + 1);
@@ -257,7 +259,7 @@ static int process_update(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
continue;
attkind_idx++;
- if (!pgqtriga_is_pkey(ev, tg, i, attkind_idx))
+ if (!pgqtriga_is_pkey(ev, i, attkind_idx))
continue;
col_ident = SPI_fname(tupdesc, i + 1);
@@ -273,8 +275,9 @@ static int process_update(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
return 1;
}
-static void process_delete(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
+static void process_delete(PgqTriggerEvent *ev, StringInfo sql)
{
+ TriggerData *tg = ev->tgdata;
HeapTuple old_row = tg->tg_trigtuple;
TupleDesc tupdesc = tg->tg_relation->rd_att;
char *col_ident;
@@ -288,7 +291,7 @@ static void process_delete(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
continue;
attkind_idx++;
- if (!pgqtriga_is_pkey(ev, tg, i, attkind_idx))
+ if (!pgqtriga_is_pkey(ev, i, attkind_idx))
continue;
col_ident = SPI_fname(tupdesc, i + 1);
col_value = SPI_getvalue(old_row, tupdesc, i + 1);
@@ -302,8 +305,9 @@ static void process_delete(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
}
}
-int pgqtriga_make_sql(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
+int pgqtriga_make_sql(PgqTriggerEvent *ev, StringInfo sql)
{
+ TriggerData *tg = ev->tgdata;
TupleDesc tupdesc;
int i;
int attcnt;
@@ -324,14 +328,11 @@ int pgqtriga_make_sql(PgqTriggerEvent *ev, TriggerData *tg, StringInfo sql)
* Determine cmdtype and op_data depending on the command type
*/
if (TRIGGER_FIRED_BY_INSERT(tg->tg_event)) {
- //appendStringInfoChar(op_type, 'I');
- process_insert(ev, tg, sql);
+ process_insert(ev, sql);
} else if (TRIGGER_FIRED_BY_UPDATE(tg->tg_event)) {
- //appendStringInfoChar(op_type, 'U');
- need_event = process_update(ev, tg, sql);
+ need_event = process_update(ev, sql);
} else if (TRIGGER_FIRED_BY_DELETE(tg->tg_event)) {
- //appendStringInfoChar(op_type, 'D');
- process_delete(ev, tg, sql);
+ process_delete(ev, sql);
} else
elog(ERROR, "logtriga fired for unhandled event");
diff --git a/sql/pgq/triggers/parsesql.c b/sql/pgq/triggers/parsesql.c
new file mode 100644
index 00000000..cdc34cab
--- /dev/null
+++ b/sql/pgq/triggers/parsesql.c
@@ -0,0 +1,221 @@
+
+#ifndef TEST
+#include <postgres.h>
+#else
+#include <ctype.h>
+#include <string.h>
+#endif
+
+#include "parsesql.h"
+
+/*
+ * Small SQL tokenizer. For cases where flex/bison is overkill.
+ *
+ * To simplify futher processing, it merges words separated
+ * with dots together. That also means it does not support
+ * whitespace/comments before and after dot.
+ *
+ * Otherwise it's relatively compatible with main parser.
+ *
+ * Return value:
+ * -1 - error
+ * 0 - end of string
+ * 1..255 - single char
+ * >255 - token code
+ */
+int sql_tokenizer(const char *sql, int *len_p, bool stdstr)
+{
+ const char *p = sql;
+ int tok;
+
+ *len_p = 0;
+ if (!*p) {
+ /* end */
+ return 0;
+ } else if (isspace(*p) || (p[0] == '-' && p[1] == '-') || (p[0] == '/' && p[1] == '*')) {
+ /* whitespace */
+ tok = T_SPACE;
+ while (1) {
+ if (p[0] == '-' && p[1] == '-') {
+ /* line comment */
+ while (*p && *p != '\n')
+ p++;
+ } else if (p[0] == '/' && p[1] == '*') {
+ /* c-comment, potentially nested */
+ int level = 1;
+ p += 2;
+ while (level) {
+ if (p[0] == '*' && p[1] == '/') {
+ level--;
+ p += 2;
+ } else if (p[0] == '/' && p[1] == '*') {
+ level++;
+ p += 2;
+ } else if (!*p) {
+ return -1;
+ } else
+ p++;
+ }
+ } else if (isspace(p[0])) {
+ /* plain whitespace */
+ while (isspace(p[0]))
+ p++;
+ } else
+ break;
+ }
+ } else if ((p[0] == '\'' && !stdstr) || ((p[0] == 'E' || p[0] == 'e') && p[1] == '\'')) {
+ /* extended string */
+ tok = T_STRING;
+ if (p[0] == '\'')
+ p++;
+ else
+ p += 2;
+ for (; *p; p++) {
+ if (p[0] == '\'') {
+ if (p[1] == '\'')
+ p++;
+ else
+ break;
+ } else if (p[0] == '\\') {
+ if (!p[1])
+ return -1;
+ p++;
+ }
+ }
+ if (*p++ != '\'')
+ return -1;
+ } else if (p[0] == '\'' && stdstr) {
+ /* standard string */
+ tok = T_STRING;
+ for (p++; *p; p++) {
+ if (p[0] == '\'') {
+ if (p[1] == '\'')
+ p++;
+ else
+ break;
+ }
+ }
+ if (*p++ != '\'')
+ return -1;
+ } else if (isalpha(*p) || (*p == '_')) {
+ /* plain/quoted words separated with '.' */
+ tok = T_WORD;
+ while (1) {
+ /* plain ident */
+ while (*p && (isalnum(*p) || *p == '_' || *p == '.'))
+ p++;
+ if (p[0] == '"') {
+ /* quoted ident */
+ for (p++; *p; p++) {
+ if (p[0] == '"') {
+ if (p[1] == '"')
+ p++;
+ else
+ break;
+ }
+ }
+ if (*p++ != '"')
+ return -1;
+ } else if (p[0] == '.') {
+ tok = T_FQIDENT;
+ p++;
+ } else {
+ break;
+ }
+ }
+ } else if (isdigit(p[0]) || (p[0] == '.' && isdigit(p[1]))) {
+ /* number */
+ tok = T_NUMBER;
+ while (*p) {
+ if (isdigit(*p) || *p == '.') {
+ p++;
+ } else if ((*p == 'e' || *p == 'E')) {
+ if (p[1] == '.' || p[1] == '+' || p[1] == '-') {
+ p += 2;
+ } else if (isdigit(p[1])) {
+ p += 2;
+ } else
+ break;
+ } else
+ break;
+ }
+ } else if (p[0] == '$') {
+ if (isdigit(p[1])) {
+ /* dollar ident */
+ tok = T_WORD;
+ for (p += 2; *p; p++) {
+ if (!isdigit(*p))
+ break;
+ }
+ } else if (isalpha(p[1]) || p[1] == '_' || p[1] == '$') {
+ /* dollar quote */
+ const char *p2, *p3;
+ tok = T_STRING;
+ p2 = strchr(p+1, '$');
+ if (!p2)
+ return -1;
+ p3 = ++p2;
+
+ while (1) {
+ p3 = strchr(p3, '$');
+ if (!p3)
+ return -1;
+ if (strncmp(p3, p, p2 - p) == 0)
+ break;
+ p3++;
+ }
+ p = p3 + (p2 - p);
+ } else
+ return -1;
+ } else if (*p == '.') {
+ /* disallow standalone dot - seems ident parsing missed it */
+ return -1;
+ } else {
+ /* return other symbols as-is */
+ tok = *p++;
+ }
+ *len_p = p - sql;
+ return tok;
+}
+
+
+#ifdef TEST
+
+/*
+ * test code
+ */
+
+const char test_sql[] =
+"\r\n\t "
+"-- foo\n"
+"/*/**//* nested *//**/*/\n"
+"select 1, .600, $1, $150, 1.44e+.1,"
+" bzo.\"fo'\"\".o\".zoo.fa, "
+"E'a\\\\ \\000 \\' baz ''',"
+"'foo''baz' from \"quoted\"\"id\";"
+"$$$$ $_$ $x$ $ $_ $_$"
+;
+
+int main(void)
+{
+ const char *sql = test_sql;
+ int tlen;
+ int tok;
+ bool stdstr = false;
+ while (1) {
+ tok = sql_tokenizer(sql, &tlen, stdstr);
+ if (tok == 0) {
+ printf("EOF\n");
+ break;
+ } else if (tok < 0) {
+ printf("ERR\n");
+ return 1;
+ }
+ printf("tok=%d len=%d str=<%.*s>\n", tok, tlen, tlen, sql);
+ sql += tlen;
+ }
+ return 0;
+}
+
+#endif
+
diff --git a/sql/pgq/triggers/parsesql.h b/sql/pgq/triggers/parsesql.h
new file mode 100644
index 00000000..80ce3e3f
--- /dev/null
+++ b/sql/pgq/triggers/parsesql.h
@@ -0,0 +1,12 @@
+
+/* multi-char tokens */
+enum SqlToken {
+ T_SPACE = 257,
+ T_STRING,
+ T_NUMBER,
+ T_WORD,
+ T_FQIDENT,
+};
+
+int sql_tokenizer(const char *sql, int *len_p, bool stdstr);
+
diff --git a/sql/pgq/triggers/pgq_triggers.sql.in b/sql/pgq/triggers/pgq_triggers.sql.in
index 1b227729..a8362bd0 100644
--- a/sql/pgq/triggers/pgq_triggers.sql.in
+++ b/sql/pgq/triggers/pgq_triggers.sql.in
@@ -16,6 +16,8 @@
-- ignore=col1[,col2] - don't look at the specified arguments
-- pkey=col1[,col2] - Set pkey fields for the table, PK autodetection will be skipped
-- backup - Put urlencoded contents of old row to ev_extra2
+-- colname=EXPR - Override field value with SQL expression. Can reference table
+-- columns. colname can be: ev_type, ev_data, ev_extra1 .. ev_extra4
--
-- Queue event fields:
-- ev_type - I/U/D
@@ -46,6 +48,8 @@ AS 'MODULE_PATHNAME', 'pgq_sqltriga' LANGUAGE C;
-- ignore=col1[,col2] - don't look at the specified arguments
-- pkey=col1[,col2] - Set pkey fields for the table, autodetection will be skipped
-- backup - Put urlencoded contents of old row to ev_extra2
+-- colname=EXPR - Override field value with SQL expression. Can reference table
+-- columns. colname can be: ev_type, ev_data, ev_extra1 .. ev_extra4
--
-- Queue event fields:
-- ev_type - I/U/D ':' pkey_column_list
diff --git a/sql/pgq/triggers/qbuilder.c b/sql/pgq/triggers/qbuilder.c
new file mode 100644
index 00000000..8492959f
--- /dev/null
+++ b/sql/pgq/triggers/qbuilder.c
@@ -0,0 +1,144 @@
+
+#include <postgres.h>
+#include <executor/spi.h>
+
+#include "qbuilder.h"
+#include "parsesql.h"
+
+/* import standard_conforming_strings */
+#if PG_VERSION_NUM >= 80500
+#include <parser/parser.h>
+#else
+#ifndef PGDLLIMPORT
+#define PGDLLIMPORT DLLIMPORT
+#endif
+extern PGDLLIMPORT bool standard_conforming_strings;
+#endif
+
+/* create QB in right context */
+struct QueryBuilder *qb_create(const struct QueryBuilderOps *ops, MemoryContext ctx)
+{
+ struct QueryBuilder *q;
+
+ q = MemoryContextAllocZero(ctx, sizeof(*q));
+ q->op = ops;
+ q->stdstr = standard_conforming_strings;
+
+ q->maxargs = 8;
+ q->arg_map = MemoryContextAlloc(ctx, q->maxargs * sizeof(int));
+
+ /* default size too large? */
+ q->sql.maxlen = 64;
+ q->sql.data = MemoryContextAlloc(ctx, q->sql.maxlen);
+ q->sql.data[0] = 0;
+ return q;
+}
+
+/* add fragment without parsing */
+void qb_add_raw(struct QueryBuilder *q, const char *str, int len)
+{
+ if (len < 0)
+ len = strlen(str);
+ appendBinaryStringInfo(&q->sql, str, len);
+}
+
+/* the ident may or may not be argument reference */
+static void qb_handle_ident(struct QueryBuilder *q, const char *ident, int len, void *arg)
+{
+ int real_idx;
+ int local_idx = -1, i;
+ char abuf[32];
+
+ /* is argument reference? */
+ real_idx = q->op->name_lookup(arg, ident, len);
+ if (real_idx < 0) {
+ qb_add_raw(q, ident, len);
+ return;
+ }
+
+ /* already referenced? */
+ for (i = 0; i < q->nargs; i++) {
+ if (q->arg_map[i] == real_idx) {
+ local_idx = i;
+ break;
+ }
+ }
+
+ /* new referece? */
+ if (local_idx < 0) {
+ if (q->nargs >= FUNC_MAX_ARGS)
+ elog(ERROR, "Too many args");
+ if (q->nargs >= q->maxargs) {
+ q->arg_map = repalloc(q->arg_map, q->maxargs * 2 * sizeof(int));
+ q->maxargs *= 2;
+ }
+ local_idx = q->nargs++;
+ q->arg_map[local_idx] = real_idx;
+ }
+
+ /* add $n to query */
+ snprintf(abuf, sizeof(abuf), "$%d", local_idx + 1);
+ return qb_add_raw(q, abuf, strlen(abuf));
+}
+
+/* add fragment with parsing - argument references are replaced with $n */
+void qb_add_parse(struct QueryBuilder *q, const char *sql, void *arg)
+{
+ int tlen, tok;
+
+ /* tokenize sql, pick out argument references */
+ while (1) {
+ tok = sql_tokenizer(sql, &tlen, q->stdstr);
+ if (!tok)
+ break;
+ if (tok < 0)
+ elog(ERROR, "QB: syntax error");
+ if (tok == T_WORD) {
+ qb_handle_ident(q, sql, tlen, arg);
+ } else {
+ qb_add_raw(q, sql, tlen);
+ }
+ sql += tlen;
+ }
+}
+
+/* prepare */
+void qb_prepare(struct QueryBuilder *q, void *arg)
+{
+ Oid types[FUNC_MAX_ARGS];
+ void *plan;
+ int i;
+
+ for (i = 0; i < q->nargs; i++)
+ types[i] = q->op->type_lookup(arg, q->arg_map[i]);
+
+ plan = SPI_prepare(q->sql.data, q->nargs, types);
+ q->plan = SPI_saveplan(plan);
+}
+
+/* lookup values and run plan. returns result from SPI_execute_plan() */
+int qb_execute(struct QueryBuilder *q, void *arg)
+{
+ Datum values[FUNC_MAX_ARGS];
+ char nulls[FUNC_MAX_ARGS];
+ int i;
+
+ for (i = 0; i < q->nargs; i++) {
+ bool isnull = false;
+ values[i] = q->op->value_lookup(arg, q->arg_map[i], &isnull);
+ nulls[i] = isnull ? 'n' : ' ';
+ }
+ return SPI_execute_plan(q->plan, values, nulls, true, 0);
+}
+
+void qb_free(struct QueryBuilder *q)
+{
+ if (!q)
+ return;
+ if (q->plan)
+ SPI_freeplan(q->plan);
+ if (q->sql.data)
+ pfree(q->sql.data);
+ pfree(q);
+}
+
diff --git a/sql/pgq/triggers/qbuilder.h b/sql/pgq/triggers/qbuilder.h
new file mode 100644
index 00000000..72b8d7bb
--- /dev/null
+++ b/sql/pgq/triggers/qbuilder.h
@@ -0,0 +1,38 @@
+
+/*
+ * Callbacks that to argument name/type/value lookups.
+ */
+struct QueryBuilderOps {
+ /* returns name index or < 0 if unknown. str is not null-terminated */
+ int (*name_lookup)(void *arg, const char *str, int len);
+
+ /* returns type oid for nr that .name_lookup returned */
+ Oid (*type_lookup)(void *arg, int nr);
+
+ /* returns value for nr that .name_lookup returned */
+ Datum (*value_lookup)(void *arg, int nr, bool *isnull);
+};
+
+/*
+ * Parsed query
+ */
+struct QueryBuilder {
+ StringInfoData sql;
+ bool stdstr;
+ const struct QueryBuilderOps *op;
+
+ void *plan;
+
+ int nargs;
+ int maxargs;
+ int *arg_map;
+};
+
+struct QueryBuilder *qb_create(const struct QueryBuilderOps *ops, MemoryContext ctx);
+void qb_add_raw(struct QueryBuilder *q, const char *str, int len);
+void qb_add_parse(struct QueryBuilder *q, const char *str, void *arg);
+void qb_free(struct QueryBuilder *q);
+
+void qb_prepare(struct QueryBuilder *q, void *arg);
+int qb_execute(struct QueryBuilder *q, void *arg);
+
diff --git a/sql/pgq/triggers/sqltriga.c b/sql/pgq/triggers/sqltriga.c
index e75bd511..056dc343 100644
--- a/sql/pgq/triggers/sqltriga.c
+++ b/sql/pgq/triggers/sqltriga.c
@@ -61,14 +61,14 @@ Datum pgq_sqltriga(PG_FUNCTION_ARGS)
pgq_prepare_event(&ev, tg, true);
- appendStringInfoChar(ev.ev_type, ev.op_type);
- appendStringInfoString(ev.ev_extra1, ev.info->table_name);
+ appendStringInfoChar(ev.field[EV_TYPE], ev.op_type);
+ appendStringInfoString(ev.field[EV_EXTRA1], ev.info->table_name);
/*
* create sql and insert if interesting
*/
- if (pgqtriga_make_sql(&ev, tg, ev.ev_data))
- pgq_insert_tg_event(&ev, tg);
+ if (pgqtriga_make_sql(&ev, ev.field[EV_DATA]))
+ pgq_insert_tg_event(&ev);
if (SPI_finish() < 0)
elog(ERROR, "SPI_finish failed");
@@ -78,7 +78,7 @@ Datum pgq_sqltriga(PG_FUNCTION_ARGS)
* before trigger skips event if NULL.
*/
skip_it:
- if (TRIGGER_FIRED_AFTER(tg->tg_event) || ev.skip)
+ if (TRIGGER_FIRED_AFTER(tg->tg_event) || ev.tgargs->skip)
return PointerGetDatum(NULL);
else if (TRIGGER_FIRED_BY_UPDATE(tg->tg_event))
return PointerGetDatum(tg->tg_newtuple);