diff options
author | Marko Kreen | 2010-05-06 09:37:25 +0000 |
---|---|---|
committer | Marko Kreen | 2010-05-07 10:47:40 +0000 |
commit | 6cf0275e9efb8316ca2737bee2bc643364b5ccb8 (patch) | |
tree | 9de8e26da993cbfafc5c2fede09773b087fb7500 | |
parent | 857d61029693d863e0aadbe76b9d7e3d94f84bc7 (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.out | 18 | ||||
-rw-r--r-- | sql/pgq/expected/sqltriga.out | 18 | ||||
-rw-r--r-- | sql/pgq/sql/logutriga.sql | 14 | ||||
-rw-r--r-- | sql/pgq/sql/sqltriga.sql | 13 | ||||
-rw-r--r-- | sql/pgq/triggers/Makefile | 3 | ||||
-rw-r--r-- | sql/pgq/triggers/common.c | 290 | ||||
-rw-r--r-- | sql/pgq/triggers/common.h | 71 | ||||
-rw-r--r-- | sql/pgq/triggers/logtriga.c | 8 | ||||
-rw-r--r-- | sql/pgq/triggers/logutriga.c | 23 | ||||
-rw-r--r-- | sql/pgq/triggers/makesql.c | 33 | ||||
-rw-r--r-- | sql/pgq/triggers/parsesql.c | 221 | ||||
-rw-r--r-- | sql/pgq/triggers/parsesql.h | 12 | ||||
-rw-r--r-- | sql/pgq/triggers/pgq_triggers.sql.in | 4 | ||||
-rw-r--r-- | sql/pgq/triggers/qbuilder.c | 144 | ||||
-rw-r--r-- | sql/pgq/triggers/qbuilder.h | 38 | ||||
-rw-r--r-- | sql/pgq/triggers/sqltriga.c | 10 |
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); |