Datum ttdummy(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger; /* to get trigger name */ char **args; /* arguments */ int attnum[2]; /* fnumbers of start/stop columns */ Datum oldon, oldoff; Datum newon, newoff; Datum *cvals; /* column values */ char *cnulls; /* column nulls */ char *relname; /* triggered relation name */ Relation rel; /* triggered relation */ HeapTuple trigtuple; HeapTuple newtuple = NULL; HeapTuple rettuple; TupleDesc tupdesc; /* tuple description */ int natts; /* # of attributes */ bool isnull; /* to know is some column NULL or not */ int ret; int i; if (!CALLED_AS_TRIGGER(fcinfo)) elog(ERROR, "ttdummy: not fired by trigger manager"); if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) elog(ERROR, "ttdummy: must be fired for row"); if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) elog(ERROR, "ttdummy: must be fired before event"); if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) elog(ERROR, "ttdummy: cannot process INSERT event"); if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) newtuple = trigdata->tg_newtuple; trigtuple = trigdata->tg_trigtuple; rel = trigdata->tg_relation; relname = SPI_getrelname(rel); /* check if TT is OFF for this relation */ if (ttoff) /* OFF - nothing to do */ { pfree(relname); return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple); } trigger = trigdata->tg_trigger; if (trigger->tgnargs != 2) elog(ERROR, "ttdummy (%s): invalid (!= 2) number of arguments %d", relname, trigger->tgnargs); args = trigger->tgargs; tupdesc = rel->rd_att; natts = tupdesc->natts; for (i = 0; i < 2; i++) { attnum[i] = SPI_fnumber(tupdesc, args[i]); if (attnum[i] <= 0) elog(ERROR, "ttdummy (%s): there is no attribute %s", relname, args[i]); if (SPI_gettypeid(tupdesc, attnum[i]) != INT4OID) elog(ERROR, "ttdummy (%s): attribute %s must be of integer type", relname, args[i]); } oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull); if (isnull) elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[0]); oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull); if (isnull) elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[1]); if (newtuple != NULL) /* UPDATE */ { newon = SPI_getbinval(newtuple, tupdesc, attnum[0], &isnull); if (isnull) elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[0]); newoff = SPI_getbinval(newtuple, tupdesc, attnum[1], &isnull); if (isnull) elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[1]); if (oldon != newon || oldoff != newoff) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("ttdummy (%s): you cannot change %s and/or %s columns (use set_ttdummy)", relname, args[0], args[1]))); if (newoff != TTDUMMY_INFINITY) { pfree(relname); /* allocated in upper executor context */ return PointerGetDatum(NULL); } } else if (oldoff != TTDUMMY_INFINITY) /* DELETE */ { pfree(relname); return PointerGetDatum(NULL); } newoff = DirectFunctionCall1(nextval, CStringGetTextDatum("ttdummy_seq")); /* nextval now returns int64; coerce down to int32 */ newoff = Int32GetDatum((int32) DatumGetInt64(newoff)); /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) elog(ERROR, "ttdummy (%s): SPI_connect returned %d", relname, ret); /* Fetch tuple values and nulls */ cvals = (Datum *) palloc(natts * sizeof(Datum)); cnulls = (char *) palloc(natts * sizeof(char)); for (i = 0; i < natts; i++) { cvals[i] = SPI_getbinval((newtuple != NULL) ? newtuple : trigtuple, tupdesc, i + 1, &isnull); cnulls[i] = (isnull) ? 'n' : ' '; } /* change date column(s) */ if (newtuple) /* UPDATE */ { cvals[attnum[0] - 1] = newoff; /* start_date eq current date */ cnulls[attnum[0] - 1] = ' '; cvals[attnum[1] - 1] = TTDUMMY_INFINITY; /* stop_date eq INFINITY */ cnulls[attnum[1] - 1] = ' '; } else /* DELETE */ { cvals[attnum[1] - 1] = newoff; /* stop_date eq current date */ cnulls[attnum[1] - 1] = ' '; } /* if there is no plan ... */ if (splan == NULL) { SPIPlanPtr pplan; Oid *ctypes; char *query; /* allocate space in preparation */ ctypes = (Oid *) palloc(natts * sizeof(Oid)); query = (char *) palloc(100 + 16 * natts); /* * Construct query: INSERT INTO _relation_ VALUES ($1, ...) */ sprintf(query, "INSERT INTO %s VALUES (", relname); for (i = 1; i <= natts; i++) { sprintf(query + strlen(query), "$%d%s", i, (i < natts) ? ", " : ")"); ctypes[i - 1] = SPI_gettypeid(tupdesc, i); } /* Prepare plan for query */ pplan = SPI_prepare(query, natts, ctypes); if (pplan == NULL) elog(ERROR, "ttdummy (%s): SPI_prepare returned %s", relname, SPI_result_code_string(SPI_result)); if (SPI_keepplan(pplan)) elog(ERROR, "ttdummy (%s): SPI_keepplan failed", relname); splan = pplan; } ret = SPI_execp(splan, cvals, cnulls, 0); if (ret < 0) elog(ERROR, "ttdummy (%s): SPI_execp returned %d", relname, ret); /* Tuple to return to upper Executor ... */ if (newtuple) /* UPDATE */ rettuple = SPI_modifytuple(rel, trigtuple, 1, &(attnum[1]), &newoff, NULL); else /* DELETE */ rettuple = trigtuple; SPI_finish(); /* don't forget say Bye to SPI mgr */ pfree(relname); return PointerGetDatum(rettuple); }
Datum check_foreign_key(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger; /* to get trigger name */ int nargs; /* # of args specified in CREATE TRIGGER */ char **args; /* arguments: as described above */ char **args_temp; int nrefs; /* number of references (== # of plans) */ char action; /* 'R'estrict | 'S'etnull | 'C'ascade */ int nkeys; /* # of key columns */ Datum *kvals; /* key values */ char *relname; /* referencing relation name */ Relation rel; /* triggered relation */ HeapTuple trigtuple = NULL; /* tuple to being changed */ HeapTuple newtuple = NULL; /* tuple to return */ TupleDesc tupdesc; /* tuple description */ EPlan *plan; /* prepared plan(s) */ Oid *argtypes = NULL; /* key types to prepare execution plan */ bool isnull; /* to know is some column NULL or not */ bool isequal = true; /* are keys in both tuples equal (in UPDATE) */ char ident[2 * NAMEDATALEN]; /* to identify myself */ int is_update = 0; int ret; int i, r; #ifdef DEBUG_QUERY elog(DEBUG4, "check_foreign_key: Enter Function"); #endif /* * Some checks first... */ /* Called by trigger manager ? */ if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */ elog(ERROR, "check_foreign_key: not fired by trigger manager"); /* Should be called for ROW trigger */ if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) /* internal error */ elog(ERROR, "check_foreign_key: must be fired for row"); /* Not should be called for INSERT */ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) /* internal error */ elog(ERROR, "check_foreign_key: cannot process INSERT events"); /* Have to check tg_trigtuple - tuple being deleted */ trigtuple = trigdata->tg_trigtuple; /* * But if this is UPDATE then we have to return tg_newtuple. Also, if key * in tg_newtuple is the same as in tg_trigtuple then nothing to do. */ is_update = 0; if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) { newtuple = trigdata->tg_newtuple; is_update = 1; } trigger = trigdata->tg_trigger; nargs = trigger->tgnargs; args = trigger->tgargs; if (nargs < 5) /* nrefs, action, key, Relation, key - at * least */ /* internal error */ elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs); nrefs = pg_atoi(args[0], sizeof(int), 0); if (nrefs < 1) /* internal error */ elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs); action = tolower((unsigned char) *(args[1])); if (action != 'r' && action != 'c' && action != 's') /* internal error */ elog(ERROR, "check_foreign_key: invalid action %s", args[1]); nargs -= 2; args += 2; nkeys = (nargs - nrefs) / (nrefs + 1); if (nkeys <= 0 || nargs != (nrefs + nkeys * (nrefs + 1))) /* internal error */ elog(ERROR, "check_foreign_key: invalid number of arguments %d for %d references", nargs + 2, nrefs); rel = trigdata->tg_relation; tupdesc = rel->rd_att; /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) /* internal error */ elog(ERROR, "check_foreign_key: SPI_connect returned %d", ret); /* * We use SPI plan preparation feature, so allocate space to place key * values. */ kvals = (Datum *) palloc(nkeys * sizeof(Datum)); /* * Construct ident string as TriggerName $ TriggeredRelationId and try to * find prepared execution plan(s). */ snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id); plan = find_plan(ident, &FPlans, &nFPlans); /* if there is no plan(s) then allocate argtypes for preparation */ if (plan->nplans <= 0) argtypes = (Oid *) palloc(nkeys * sizeof(Oid)); /* * else - check that we have exactly nrefs plan(s) ready */ else if (plan->nplans != nrefs) /* internal error */ elog(ERROR, "%s: check_foreign_key: # of plans changed in meantime", trigger->tgname); /* For each column in key ... */ for (i = 0; i < nkeys; i++) { /* get index of column in tuple */ int fnumber = SPI_fnumber(tupdesc, args[i]); /* Bad guys may give us un-existing column in CREATE TRIGGER */ if (fnumber < 0) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("there is no attribute \"%s\" in relation \"%s\"", args[i], SPI_getrelname(rel)))); /* Well, get binary (in internal format) value of column */ kvals[i] = SPI_getbinval(trigtuple, tupdesc, fnumber, &isnull); /* * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()! * DON'T FORGET return tuple! Executor inserts tuple you're returning! * If you return NULL then nothing will be inserted! */ if (isnull) { SPI_finish(); return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple); } /* * If UPDATE then get column value from new tuple being inserted and * compare is this the same as old one. For the moment we use string * presentation of values... */ if (newtuple != NULL) { char *oldval = SPI_getvalue(trigtuple, tupdesc, fnumber); char *newval; /* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */ if (oldval == NULL) /* internal error */ elog(ERROR, "check_foreign_key: SPI_getvalue returned %d", SPI_result); newval = SPI_getvalue(newtuple, tupdesc, fnumber); if (newval == NULL || strcmp(oldval, newval) != 0) isequal = false; } if (plan->nplans <= 0) /* Get typeId of column */ argtypes[i] = SPI_gettypeid(tupdesc, fnumber); } args_temp = args; nargs -= nkeys; args += nkeys; /* * If we have to prepare plans ... */ if (plan->nplans <= 0) { SPIPlanPtr pplan; char sql[8192]; char **args2 = args; plan->splan = (SPIPlanPtr *) malloc(nrefs * sizeof(SPIPlanPtr)); for (r = 0; r < nrefs; r++) { relname = args2[0]; /*--------- * For 'R'estrict action we construct SELECT query: * * SELECT 1 * FROM _referencing_relation_ * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]] * * to check is tuple referenced or not. *--------- */ if (action == 'r') snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); /*--------- * For 'C'ascade action we construct DELETE query * * DELETE * FROM _referencing_relation_ * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]] * * to delete all referencing tuples. *--------- */ /* * Max : Cascade with UPDATE query i create update query that * updates new key values in referenced tables */ else if (action == 'c') { if (is_update == 1) { int fn; char *nv; int k; snprintf(sql, sizeof(sql), "update %s set ", relname); for (k = 1; k <= nkeys; k++) { int is_char_type = 0; char *type; fn = SPI_fnumber(tupdesc, args_temp[k - 1]); nv = SPI_getvalue(newtuple, tupdesc, fn); type = SPI_gettype(tupdesc, fn); if ((strcmp(type, "text") && strcmp(type, "varchar") && strcmp(type, "char") && strcmp(type, "bpchar") && strcmp(type, "date") && strcmp(type, "timestamp")) == 0) is_char_type = 1; #ifdef DEBUG_QUERY elog(DEBUG4, "check_foreign_key Debug value %s type %s %d", nv, type, is_char_type); #endif /* * is_char_type =1 i set ' ' for define a new value */ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " %s = %s%s%s %s ", args2[k], (is_char_type > 0) ? "'" : "", nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : ""); is_char_type = 0; } strcat(sql, " where "); } else /* DELETE */ snprintf(sql, sizeof(sql), "delete from %s where ", relname); } /* * For 'S'etnull action we construct UPDATE query - UPDATE * _referencing_relation_ SET Fkey1 null [, Fkey2 null [...]] * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]] - to set key columns in * all referencing tuples to NULL. */ else if (action == 's') { snprintf(sql, sizeof(sql), "update %s set ", relname); for (i = 1; i <= nkeys; i++) { snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = null%s", args2[i], (i < nkeys) ? ", " : ""); } strcat(sql, " where "); } /* Construct WHERE qual */ for (i = 1; i <= nkeys; i++) { snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", args2[i], i, (i < nkeys) ? "and " : ""); } /* Prepare plan for query */ pplan = SPI_prepare(sql, nkeys, argtypes); if (pplan == NULL) /* internal error */ elog(ERROR, "check_foreign_key: SPI_prepare returned %d", SPI_result); /* * Remember that SPI_prepare places plan in current memory context * - so, we have to save plan in Top memory context for latter * use. */ pplan = SPI_saveplan(pplan); if (pplan == NULL) /* internal error */ elog(ERROR, "check_foreign_key: SPI_saveplan returned %d", SPI_result); plan->splan[r] = pplan; args2 += nkeys + 1; /* to the next relation */ } plan->nplans = nrefs; #ifdef DEBUG_QUERY elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql); #endif } /* * If UPDATE and key is not changed ... */ if (newtuple != NULL && isequal) { SPI_finish(); return PointerGetDatum(newtuple); } /* * Ok, execute prepared plan(s). */ for (r = 0; r < nrefs; r++) { /* * For 'R'estrict we may to execute plan for one tuple only, for other * actions - for all tuples. */ int tcount = (action == 'r') ? 1 : 0; relname = args[0]; snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id); plan = find_plan(ident, &FPlans, &nFPlans); ret = SPI_execp(plan->splan[r], kvals, NULL, tcount); /* we have no NULLs - so we pass ^^^^ here */ if (ret < 0) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("SPI_execp returned %d", ret))); /* If action is 'R'estrict ... */ if (action == 'r') { /* If there is tuple returned by SELECT then ... */ if (SPI_processed > 0) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("\"%s\": tuple is referenced in \"%s\"", trigger->tgname, relname))); } else { #ifdef REFINT_VERBOSE elog(NOTICE, "%s: %d tuple(s) of %s are %s", trigger->tgname, SPI_processed, relname, (action == 'c') ? "deleted" : "set to null"); #endif } args += nkeys + 1; /* to the next relation */ } SPI_finish(); return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple); }
Datum check_primary_key(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger; /* to get trigger name */ int nargs; /* # of args specified in CREATE TRIGGER */ char **args; /* arguments: column names and table name */ int nkeys; /* # of key columns (= nargs / 2) */ Datum *kvals; /* key values */ char *relname; /* referenced relation name */ Relation rel; /* triggered relation */ HeapTuple tuple = NULL; /* tuple to return */ TupleDesc tupdesc; /* tuple description */ EPlan *plan; /* prepared plan */ Oid *argtypes = NULL; /* key types to prepare execution plan */ bool isnull; /* to know is some column NULL or not */ char ident[2 * NAMEDATALEN]; /* to identify myself */ int ret; int i; #ifdef DEBUG_QUERY elog(DEBUG4, "check_primary_key: Enter Function"); #endif /* * Some checks first... */ /* Called by trigger manager ? */ if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */ elog(ERROR, "check_primary_key: not fired by trigger manager"); /* Should be called for ROW trigger */ if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) /* internal error */ elog(ERROR, "check_primary_key: must be fired for row"); /* If INSERTion then must check Tuple to being inserted */ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) tuple = trigdata->tg_trigtuple; /* Not should be called for DELETE */ else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) /* internal error */ elog(ERROR, "check_primary_key: cannot process DELETE events"); /* If UPDATion the must check new Tuple, not old one */ else tuple = trigdata->tg_newtuple; trigger = trigdata->tg_trigger; nargs = trigger->tgnargs; args = trigger->tgargs; if (nargs % 2 != 1) /* odd number of arguments! */ /* internal error */ elog(ERROR, "check_primary_key: odd number of arguments should be specified"); nkeys = nargs / 2; relname = args[nkeys]; rel = trigdata->tg_relation; tupdesc = rel->rd_att; /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) /* internal error */ elog(ERROR, "check_primary_key: SPI_connect returned %d", ret); /* * We use SPI plan preparation feature, so allocate space to place key * values. */ kvals = (Datum *) palloc(nkeys * sizeof(Datum)); /* * Construct ident string as TriggerName $ TriggeredRelationId and try to * find prepared execution plan. */ snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id); plan = find_plan(ident, &PPlans, &nPPlans); /* if there is no plan then allocate argtypes for preparation */ if (plan->nplans <= 0) argtypes = (Oid *) palloc(nkeys * sizeof(Oid)); /* For each column in key ... */ for (i = 0; i < nkeys; i++) { /* get index of column in tuple */ int fnumber = SPI_fnumber(tupdesc, args[i]); /* Bad guys may give us un-existing column in CREATE TRIGGER */ if (fnumber < 0) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("there is no attribute \"%s\" in relation \"%s\"", args[i], SPI_getrelname(rel)))); /* Well, get binary (in internal format) value of column */ kvals[i] = SPI_getbinval(tuple, tupdesc, fnumber, &isnull); /* * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()! * DON'T FORGET return tuple! Executor inserts tuple you're returning! * If you return NULL then nothing will be inserted! */ if (isnull) { SPI_finish(); return PointerGetDatum(tuple); } if (plan->nplans <= 0) /* Get typeId of column */ argtypes[i] = SPI_gettypeid(tupdesc, fnumber); } /* * If we have to prepare plan ... */ if (plan->nplans <= 0) { SPIPlanPtr pplan; char sql[8192]; /* * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 = * $1 [AND Pkey2 = $2 [...]] */ snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); for (i = 0; i < nkeys; i++) { snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : ""); } /* Prepare plan for query */ pplan = SPI_prepare(sql, nkeys, argtypes); if (pplan == NULL) /* internal error */ elog(ERROR, "check_primary_key: SPI_prepare returned %d", SPI_result); /* * Remember that SPI_prepare places plan in current memory context - * so, we have to save plan in Top memory context for latter use. */ pplan = SPI_saveplan(pplan); if (pplan == NULL) /* internal error */ elog(ERROR, "check_primary_key: SPI_saveplan returned %d", SPI_result); plan->splan = (SPIPlanPtr *) malloc(sizeof(SPIPlanPtr)); *(plan->splan) = pplan; plan->nplans = 1; } /* * Ok, execute prepared plan. */ ret = SPI_execp(*(plan->splan), kvals, NULL, 1); /* we have no NULLs - so we pass ^^^^ here */ if (ret < 0) /* internal error */ elog(ERROR, "check_primary_key: SPI_execp returned %d", ret); /* * If there are no tuples returned by SELECT then ... */ if (SPI_processed == 0) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("tuple references non-existent key"), errdetail("Trigger \"%s\" found tuple referencing non-existent key in \"%s\".", trigger->tgname, relname))); SPI_finish(); return PointerGetDatum(tuple); }
Datum /* have to return HeapTuple to Executor */ timetravel(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger; /* to get trigger name */ int argc; char **args; /* arguments */ int attnum[MaxAttrNum]; /* fnumbers of start/stop columns */ Datum oldtimeon, oldtimeoff; Datum newtimeon, newtimeoff, newuser, nulltext; Datum *cvals; /* column values */ char *cnulls; /* column nulls */ char *relname; /* triggered relation name */ Relation rel; /* triggered relation */ HeapTuple trigtuple; HeapTuple newtuple = NULL; HeapTuple rettuple; TupleDesc tupdesc; /* tuple description */ int natts; /* # of attributes */ EPlan *plan; /* prepared plan */ char ident[2 * NAMEDATALEN]; bool isnull; /* to know is some column NULL or not */ bool isinsert = false; int ret; int i; /* * Some checks first... */ /* Called by trigger manager ? */ if (!CALLED_AS_TRIGGER(fcinfo)) elog(ERROR, "timetravel: not fired by trigger manager"); /* Should be called for ROW trigger */ if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) elog(ERROR, "timetravel: must be fired for row"); /* Should be called BEFORE */ if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) elog(ERROR, "timetravel: must be fired before event"); /* INSERT ? */ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) isinsert = true; if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) newtuple = trigdata->tg_newtuple; trigtuple = trigdata->tg_trigtuple; rel = trigdata->tg_relation; relname = SPI_getrelname(rel); /* check if TT is OFF for this relation */ if (0 == findTTStatus(relname)) { /* OFF - nothing to do */ pfree(relname); return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple); } trigger = trigdata->tg_trigger; argc = trigger->tgnargs; if (argc != MinAttrNum && argc != MaxAttrNum) elog(ERROR, "timetravel (%s): invalid (!= %d or %d) number of arguments %d", relname, MinAttrNum, MaxAttrNum, trigger->tgnargs); args = trigger->tgargs; tupdesc = rel->rd_att; natts = tupdesc->natts; for (i = 0; i < MinAttrNum; i++) { attnum[i] = SPI_fnumber(tupdesc, args[i]); if (attnum[i] < 0) elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]); if (SPI_gettypeid(tupdesc, attnum[i]) != ABSTIMEOID) elog(ERROR, "timetravel (%s): attribute %s must be of abstime type", relname, args[i]); } for (; i < argc; i++) { attnum[i] = SPI_fnumber(tupdesc, args[i]); if (attnum[i] < 0) elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]); if (SPI_gettypeid(tupdesc, attnum[i]) != TEXTOID) elog(ERROR, "timetravel (%s): attribute %s must be of text type", relname, args[i]); } /* create fields containing name */ newuser = CStringGetTextDatum(GetUserNameFromId(GetUserId())); nulltext = (Datum) NULL; if (isinsert) { /* INSERT */ int chnattrs = 0; int chattrs[MaxAttrNum]; Datum newvals[MaxAttrNum]; char newnulls[MaxAttrNum]; oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull); if (isnull) { newvals[chnattrs] = GetCurrentAbsoluteTime(); newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_time_on]; chnattrs++; } oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull); if (isnull) { if ((chnattrs == 0 && DatumGetInt32(oldtimeon) >= NOEND_ABSTIME) || (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) >= NOEND_ABSTIME)) elog(ERROR, "timetravel (%s): %s is infinity", relname, args[a_time_on]); newvals[chnattrs] = NOEND_ABSTIME; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_time_off]; chnattrs++; } else { if ((chnattrs == 0 && DatumGetInt32(oldtimeon) > DatumGetInt32(oldtimeoff)) || (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) > DatumGetInt32(oldtimeoff))) elog(ERROR, "timetravel (%s): %s gt %s", relname, args[a_time_on], args[a_time_off]); } pfree(relname); if (chnattrs <= 0) return PointerGetDatum(trigtuple); if (argc == MaxAttrNum) { /* clear update_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = 'n'; chattrs[chnattrs] = attnum[a_upd_user]; chnattrs++; /* clear delete_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = 'n'; chattrs[chnattrs] = attnum[a_del_user]; chnattrs++; /* set insert_user value */ newvals[chnattrs] = newuser; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_ins_user]; chnattrs++; } rettuple = SPI_modifytuple(rel, trigtuple, chnattrs, chattrs, newvals, newnulls); return PointerGetDatum(rettuple); /* end of INSERT */ } /* UPDATE/DELETE: */ oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]); oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]); /* * If DELETE/UPDATE of tuple with stop_date neq INFINITY then say upper * Executor to skip operation for this tuple */ if (newtuple != NULL) { /* UPDATE */ newtimeon = SPI_getbinval(newtuple, tupdesc, attnum[a_time_on], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]); newtimeoff = SPI_getbinval(newtuple, tupdesc, attnum[a_time_off], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]); if (oldtimeon != newtimeon || oldtimeoff != newtimeoff) elog(ERROR, "timetravel (%s): you cannot change %s and/or %s columns (use set_timetravel)", relname, args[a_time_on], args[a_time_off]); } if (oldtimeoff != NOEND_ABSTIME) { /* current record is a deleted/updated record */ pfree(relname); return PointerGetDatum(NULL); } newtimeoff = GetCurrentAbsoluteTime(); /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) elog(ERROR, "timetravel (%s): SPI_connect returned %d", relname, ret); /* Fetch tuple values and nulls */ cvals = (Datum *) palloc(natts * sizeof(Datum)); cnulls = (char *) palloc(natts * sizeof(char)); for (i = 0; i < natts; i++) { cvals[i] = SPI_getbinval(trigtuple, tupdesc, i + 1, &isnull); cnulls[i] = (isnull) ? 'n' : ' '; } /* change date column(s) */ cvals[attnum[a_time_off] - 1] = newtimeoff; /* stop_date eq current date */ cnulls[attnum[a_time_off] - 1] = ' '; if (!newtuple) { /* DELETE */ if (argc == MaxAttrNum) { cvals[attnum[a_del_user] - 1] = newuser; /* set delete user */ cnulls[attnum[a_del_user] - 1] = ' '; } } /* * Construct ident string as TriggerName $ TriggeredRelationId and try to * find prepared execution plan. */ snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id); plan = find_plan(ident, &Plans, &nPlans); /* if there is no plan ... */ if (plan->splan == NULL) { SPIPlanPtr pplan; Oid *ctypes; char sql[8192]; char separ = ' '; /* allocate ctypes for preparation */ ctypes = (Oid *) palloc(natts * sizeof(Oid)); /* * Construct query: INSERT INTO _relation_ VALUES ($1, ...) */ snprintf(sql, sizeof(sql), "INSERT INTO %s VALUES (", relname); for (i = 1; i <= natts; i++) { ctypes[i - 1] = SPI_gettypeid(tupdesc, i); if (!(tupdesc->attrs[i - 1]->attisdropped)) /* skip dropped columns */ { snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%c$%d", separ, i); separ = ','; } } snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ")"); elog(DEBUG4, "timetravel (%s) update: sql: %s", relname, sql); /* Prepare plan for query */ pplan = SPI_prepare(sql, natts, ctypes); if (pplan == NULL) elog(ERROR, "timetravel (%s): SPI_prepare returned %d", relname, SPI_result); /* * Remember that SPI_prepare places plan in current memory context - * so, we have to save plan in Top memory context for latter use. */ pplan = SPI_saveplan(pplan); if (pplan == NULL) elog(ERROR, "timetravel (%s): SPI_saveplan returned %d", relname, SPI_result); plan->splan = pplan; } /* * Ok, execute prepared plan. */ ret = SPI_execp(plan->splan, cvals, cnulls, 0); if (ret < 0) elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret); /* Tuple to return to upper Executor ... */ if (newtuple) { /* UPDATE */ int chnattrs = 0; int chattrs[MaxAttrNum]; Datum newvals[MaxAttrNum]; char newnulls[MaxAttrNum]; newvals[chnattrs] = newtimeoff; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_time_on]; chnattrs++; newvals[chnattrs] = NOEND_ABSTIME; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_time_off]; chnattrs++; if (argc == MaxAttrNum) { /* set update_user value */ newvals[chnattrs] = newuser; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_upd_user]; chnattrs++; /* clear delete_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = 'n'; chattrs[chnattrs] = attnum[a_del_user]; chnattrs++; /* set insert_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = 'n'; chattrs[chnattrs] = attnum[a_ins_user]; chnattrs++; } rettuple = SPI_modifytuple(rel, newtuple, chnattrs, chattrs, newvals, newnulls); /* * SPI_copytuple allocates tmptuple in upper executor context - have * to free allocation using SPI_pfree */ /* SPI_pfree(tmptuple); */ } else /* DELETE case */ rettuple = trigtuple; SPI_finish(); /* don't forget say Bye to SPI mgr */ pfree(relname); return PointerGetDatum(rettuple); }
Datum funny_dup17(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; TransactionId *xid; int *level; bool *recursion; Relation rel; TupleDesc tupdesc; HeapTuple tuple; char *query, *fieldval, *fieldtype; char *when; int inserted; int selected = 0; int ret; if (!CALLED_AS_TRIGGER(fcinfo)) elog(ERROR, "funny_dup17: not fired by trigger manager"); tuple = trigdata->tg_trigtuple; rel = trigdata->tg_relation; tupdesc = rel->rd_att; if (TRIGGER_FIRED_BEFORE(trigdata->tg_event)) { xid = &fd17b_xid; level = &fd17b_level; recursion = &fd17b_recursion; when = "BEFORE"; } else { xid = &fd17a_xid; level = &fd17a_level; recursion = &fd17a_recursion; when = "AFTER "; } if (!TransactionIdIsCurrentTransactionId(*xid)) { *xid = GetCurrentTransactionId(); *level = 0; *recursion = true; } if (*level == 17) { *recursion = false; return PointerGetDatum(tuple); } if (!(*recursion)) return PointerGetDatum(tuple); (*level)++; SPI_connect(); fieldval = SPI_getvalue(tuple, tupdesc, 1); fieldtype = SPI_gettype(tupdesc, 1); query = (char *) palloc(100 + NAMEDATALEN * 3 + strlen(fieldval) + strlen(fieldtype)); sprintf(query, "insert into %s select * from %s where %s = '%s'::%s", SPI_getrelname(rel), SPI_getrelname(rel), SPI_fname(tupdesc, 1), fieldval, fieldtype); if ((ret = SPI_exec(query, 0)) < 0) elog(ERROR, "funny_dup17 (fired %s) on level %3d: SPI_exec (insert ...) returned %d", when, *level, ret); inserted = SPI_processed; sprintf(query, "select count (*) from %s where %s = '%s'::%s", SPI_getrelname(rel), SPI_fname(tupdesc, 1), fieldval, fieldtype); if ((ret = SPI_exec(query, 0)) < 0) elog(ERROR, "funny_dup17 (fired %s) on level %3d: SPI_exec (select ...) returned %d", when, *level, ret); if (SPI_processed > 0) { selected = DatumGetInt32(DirectFunctionCall1(int4in, CStringGetDatum(SPI_getvalue( SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1 )))); } elog(DEBUG4, "funny_dup17 (fired %s) on level %3d: %d/%d tuples inserted/selected", when, *level, inserted, selected); SPI_finish(); (*level)--; if (*level == 0) *xid = InvalidTransactionId; return PointerGetDatum(tuple); }
static PyObject * PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv) { TriggerData *tdata = (TriggerData *) fcinfo->context; PyObject *pltname, *pltevent, *pltwhen, *pltlevel, *pltrelid, *plttablename, *plttableschema; PyObject *pltargs, *pytnew, *pytold; PyObject *volatile pltdata = NULL; char *stroid; PG_TRY(); { pltdata = PyDict_New(); if (!pltdata) PLy_elog(ERROR, "could not create new dictionary while building trigger arguments"); pltname = PyString_FromString(tdata->tg_trigger->tgname); PyDict_SetItemString(pltdata, "name", pltname); Py_DECREF(pltname); stroid = DatumGetCString(DirectFunctionCall1(oidout, ObjectIdGetDatum(tdata->tg_relation->rd_id))); pltrelid = PyString_FromString(stroid); PyDict_SetItemString(pltdata, "relid", pltrelid); Py_DECREF(pltrelid); pfree(stroid); stroid = SPI_getrelname(tdata->tg_relation); plttablename = PyString_FromString(stroid); PyDict_SetItemString(pltdata, "table_name", plttablename); Py_DECREF(plttablename); pfree(stroid); stroid = SPI_getnspname(tdata->tg_relation); plttableschema = PyString_FromString(stroid); PyDict_SetItemString(pltdata, "table_schema", plttableschema); Py_DECREF(plttableschema); pfree(stroid); if (TRIGGER_FIRED_BEFORE(tdata->tg_event)) pltwhen = PyString_FromString("BEFORE"); else if (TRIGGER_FIRED_AFTER(tdata->tg_event)) pltwhen = PyString_FromString("AFTER"); else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event)) pltwhen = PyString_FromString("INSTEAD OF"); else { elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event); pltwhen = NULL; /* keep compiler quiet */ } PyDict_SetItemString(pltdata, "when", pltwhen); Py_DECREF(pltwhen); if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) { pltlevel = PyString_FromString("ROW"); PyDict_SetItemString(pltdata, "level", pltlevel); Py_DECREF(pltlevel); if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) { pltevent = PyString_FromString("INSERT"); PyDict_SetItemString(pltdata, "old", Py_None); pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); *rv = tdata->tg_trigtuple; } else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) { pltevent = PyString_FromString("DELETE"); PyDict_SetItemString(pltdata, "new", Py_None); pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "old", pytold); Py_DECREF(pytold); *rv = tdata->tg_trigtuple; } else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) { pltevent = PyString_FromString("UPDATE"); pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "old", pytold); Py_DECREF(pytold); *rv = tdata->tg_newtuple; } else { elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); pltevent = NULL; /* keep compiler quiet */ } PyDict_SetItemString(pltdata, "event", pltevent); Py_DECREF(pltevent); } else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event)) { pltlevel = PyString_FromString("STATEMENT"); PyDict_SetItemString(pltdata, "level", pltlevel); Py_DECREF(pltlevel); PyDict_SetItemString(pltdata, "old", Py_None); PyDict_SetItemString(pltdata, "new", Py_None); *rv = NULL; if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) pltevent = PyString_FromString("INSERT"); else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) pltevent = PyString_FromString("DELETE"); else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) pltevent = PyString_FromString("UPDATE"); else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) pltevent = PyString_FromString("TRUNCATE"); else { elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); pltevent = NULL; /* keep compiler quiet */ } PyDict_SetItemString(pltdata, "event", pltevent); Py_DECREF(pltevent); } else elog(ERROR, "unrecognized LEVEL tg_event: %u", tdata->tg_event); if (tdata->tg_trigger->tgnargs) { /* * all strings... */ int i; PyObject *pltarg; pltargs = PyList_New(tdata->tg_trigger->tgnargs); for (i = 0; i < tdata->tg_trigger->tgnargs; i++) { pltarg = PyString_FromString(tdata->tg_trigger->tgargs[i]); /* * stolen, don't Py_DECREF */ PyList_SetItem(pltargs, i, pltarg); } } else { Py_INCREF(Py_None); pltargs = Py_None; } PyDict_SetItemString(pltdata, "args", pltargs); Py_DECREF(pltargs); } PG_CATCH(); { Py_XDECREF(pltdata); PG_RE_THROW(); } PG_END_TRY(); return pltdata; }
Datum autoinc(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger; /* to get trigger name */ int nargs; /* # of arguments */ int *chattrs; /* attnums of attributes to change */ int chnattrs = 0; /* # of above */ Datum *newvals; /* vals of above */ char **args; /* arguments */ char *relname; /* triggered relation name */ Relation rel; /* triggered relation */ HeapTuple rettuple = NULL; TupleDesc tupdesc; /* tuple description */ bool isnull; int i; if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */ elog(ERROR, "not fired by trigger manager"); if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) /* internal error */ elog(ERROR, "must be fired for row"); if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) /* internal error */ elog(ERROR, "must be fired before event"); if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) rettuple = trigdata->tg_trigtuple; else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) rettuple = trigdata->tg_newtuple; else /* internal error */ elog(ERROR, "cannot process DELETE events"); rel = trigdata->tg_relation; relname = SPI_getrelname(rel); trigger = trigdata->tg_trigger; nargs = trigger->tgnargs; if (nargs <= 0 || nargs % 2 != 0) /* internal error */ elog(ERROR, "autoinc (%s): even number gt 0 of arguments was expected", relname); args = trigger->tgargs; tupdesc = rel->rd_att; chattrs = (int *) palloc(nargs / 2 * sizeof(int)); newvals = (Datum *) palloc(nargs / 2 * sizeof(Datum)); for (i = 0; i < nargs;) { int attnum = SPI_fnumber(tupdesc, args[i]); int32 val; Datum seqname; if (attnum < 0) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("\"%s\" has no attribute \"%s\"", relname, args[i]))); if (SPI_gettypeid(tupdesc, attnum) != INT4OID) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("attribute \"%s\" of \"%s\" must be type INT4", args[i], relname))); val = DatumGetInt32(SPI_getbinval(rettuple, tupdesc, attnum, &isnull)); if (!isnull && val != 0) { i += 2; continue; } i++; chattrs[chnattrs] = attnum; seqname = CStringGetTextDatum(args[i]); newvals[chnattrs] = DirectFunctionCall1(nextval, seqname); /* nextval now returns int64; coerce down to int32 */ newvals[chnattrs] = Int32GetDatum((int32) DatumGetInt64(newvals[chnattrs])); if (DatumGetInt32(newvals[chnattrs]) == 0) { newvals[chnattrs] = DirectFunctionCall1(nextval, seqname); newvals[chnattrs] = Int32GetDatum((int32) DatumGetInt64(newvals[chnattrs])); } pfree(DatumGetTextP(seqname)); chnattrs++; i++; } if (chnattrs > 0) { rettuple = SPI_modifytuple(rel, rettuple, chnattrs, chattrs, newvals, NULL); if (rettuple == NULL) /* internal error */ elog(ERROR, "autoinc (%s): %d returned by SPI_modifytuple", relname, SPI_result); } pfree(relname); pfree(chattrs); pfree(newvals); return PointerGetDatum(rettuple); }
/* table_log() trigger function for logging table changes parameter: - log table name (optional) return: - trigger data (for Pg) */ Datum table_log(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; int ret; char query[250 + NAMEDATALEN]; /* for getting table infos (250 chars (+ one times the length of all names) should be enough) */ int number_columns = 0; /* counts the number columns in the table */ int number_columns_log = 0; /* counts the number columns in the table */ char *orig_schema; char *log_schema; char *log_table; int use_session_user = 0; /* should we write the current (session) user to the log table? */ /* * Some checks first... */ #ifdef TABLE_LOG_DEBUG elog(NOTICE, "start table_log()"); #endif /* TABLE_LOG_DEBUG */ /* called by trigger manager? */ if (!CALLED_AS_TRIGGER(fcinfo)) { elog(ERROR, "table_log: not fired by trigger manager"); } /* must only be called for ROW trigger */ if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event)) { elog(ERROR, "table_log: can't process STATEMENT events"); } /* must only be called AFTER */ if (TRIGGER_FIRED_BEFORE(trigdata->tg_event)) { elog(ERROR, "table_log: must be fired after event"); } /* now connect to SPI manager */ ret = SPI_connect(); if (ret != SPI_OK_CONNECT) { elog(ERROR, "table_log: SPI_connect returned %d", ret); } /* get schema name for the table, in case we need it later */ orig_schema = get_namespace_name(RelationGetNamespace(trigdata->tg_relation)); #ifdef TABLE_LOG_DEBUG elog(NOTICE, "prechecks done, now getting original table attributes"); #endif /* TABLE_LOG_DEBUG */ number_columns = count_columns(trigdata->tg_relation->rd_att); if (number_columns < 1) { elog(ERROR, "table_log: number of columns in table is < 1, can this happen?"); } #ifdef TABLE_LOG_DEBUG elog(NOTICE, "number columns in orig table: %i", number_columns); #endif /* TABLE_LOG_DEBUG */ if (trigdata->tg_trigger->tgnargs > 3) { elog(ERROR, "table_log: too many arguments to trigger"); } /* name of the log schema */ if (trigdata->tg_trigger->tgnargs > 2) { /* check if a log schema argument is given, if yes, use it */ log_schema = trigdata->tg_trigger->tgargs[2]; } else { /* if no, use orig_schema */ log_schema = orig_schema; } /* should we write the current user? */ if (trigdata->tg_trigger->tgnargs > 1) { /* check if a second argument is given */ /* if yes, use it, if it is true */ if (atoi(trigdata->tg_trigger->tgargs[1]) == 1) { use_session_user = 1; #ifdef TABLE_LOG_DEBUG elog(NOTICE, "will write session user to 'trigger_user'"); #endif /* TABLE_LOG_DEBUG */ } } /* name of the log table */ if (trigdata->tg_trigger->tgnargs > 0) { /* check if a logtable argument is given */ /* if yes, use it */ log_table = (char *) palloc((strlen(trigdata->tg_trigger->tgargs[0]) + 2) * sizeof(char)); sprintf(log_table, "%s", trigdata->tg_trigger->tgargs[0]); } else { /* if no, use 'table name' + '_log' */ log_table = (char *) palloc((strlen(do_quote_ident(SPI_getrelname(trigdata->tg_relation))) + 5) * sizeof(char)); sprintf(log_table, "%s_log", SPI_getrelname(trigdata->tg_relation)); } #ifdef TABLE_LOG_DEBUG elog(NOTICE, "log table: %s", log_table); #endif /* TABLE_LOG_DEBUG */ #ifdef TABLE_LOG_DEBUG elog(NOTICE, "now check, if log table exists"); #endif /* TABLE_LOG_DEBUG */ /* get the number columns in the table */ snprintf(query, 249, "%s.%s", do_quote_ident(log_schema), do_quote_ident(log_table)); number_columns_log = count_columns(RelationNameGetTupleDesc(query)); if (number_columns_log < 1) { elog(ERROR, "could not get number columns in relation %s", log_table); } #ifdef TABLE_LOG_DEBUG elog(NOTICE, "number columns in log table: %i", number_columns_log); #endif /* TABLE_LOG_DEBUG */ /* check if the logtable has 3 (or now 4) columns more than our table */ /* +1 if we should write the session user */ if (use_session_user == 0) { /* without session user */ if (number_columns_log != number_columns + 3 && number_columns_log != number_columns + 4) { elog(ERROR, "number colums in relation %s(%d) does not match columns in %s(%d)", SPI_getrelname(trigdata->tg_relation), number_columns, log_table, number_columns_log); } } else { /* with session user */ if (number_columns_log != number_columns + 3 + 1 && number_columns_log != number_columns + 4 + 1) { elog(ERROR, "number colums in relation %s does not match columns in %s", SPI_getrelname(trigdata->tg_relation), log_table); } } #ifdef TABLE_LOG_DEBUG elog(NOTICE, "log table OK"); #endif /* TABLE_LOG_DEBUG */ /* For each column in key ... */ #ifdef TABLE_LOG_DEBUG elog(NOTICE, "copy data ..."); #endif /* TABLE_LOG_DEBUG */ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) { /* trigger called from INSERT */ #ifdef TABLE_LOG_DEBUG elog(NOTICE, "mode: INSERT -> new"); #endif /* TABLE_LOG_DEBUG */ __table_log(trigdata, "INSERT", "new", trigdata->tg_trigtuple, number_columns, log_table, use_session_user, log_schema); } else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) { /* trigger called from UPDATE */ #ifdef TABLE_LOG_DEBUG elog(NOTICE, "mode: UPDATE -> old"); #endif /* TABLE_LOG_DEBUG */ __table_log(trigdata, "UPDATE", "old", trigdata->tg_trigtuple, number_columns, log_table, use_session_user, log_schema); #ifdef TABLE_LOG_DEBUG elog(NOTICE, "mode: UPDATE -> new"); #endif /* TABLE_LOG_DEBUG */ __table_log(trigdata, "UPDATE", "new", trigdata->tg_newtuple, number_columns, log_table, use_session_user, log_schema); } else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) { /* trigger called from DELETE */ #ifdef TABLE_LOG_DEBUG elog(NOTICE, "mode: DELETE -> old"); #endif /* TABLE_LOG_DEBUG */ __table_log(trigdata, "DELETE", "old", trigdata->tg_trigtuple, number_columns, log_table, use_session_user, log_schema); } else { elog(ERROR, "trigger fired by unknown event"); } #ifdef TABLE_LOG_DEBUG elog(NOTICE, "cleanup, trigger done"); #endif /* TABLE_LOG_DEBUG */ /* clean up */ pfree(log_table); /* close SPI connection */ SPI_finish(); /* return trigger data */ return PointerGetDatum(trigdata->tg_trigtuple); }
Datum moddatetime(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger; /* to get trigger name */ int nargs; /* # of arguments */ int attnum; /* positional number of field to change */ Oid atttypid; /* type OID of field to change */ Datum newdt; /* The current datetime. */ char **args; /* arguments */ char *relname; /* triggered relation name */ Relation rel; /* triggered relation */ HeapTuple rettuple = NULL; TupleDesc tupdesc; /* tuple description */ if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */ elog(ERROR, "moddatetime: not fired by trigger manager"); if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) /* internal error */ elog(ERROR, "moddatetime: must be fired for row"); if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) /* internal error */ elog(ERROR, "moddatetime: must be fired before event"); if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) /* internal error */ elog(ERROR, "moddatetime: cannot process INSERT events"); else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) rettuple = trigdata->tg_newtuple; else /* internal error */ elog(ERROR, "moddatetime: cannot process DELETE events"); rel = trigdata->tg_relation; relname = SPI_getrelname(rel); trigger = trigdata->tg_trigger; nargs = trigger->tgnargs; if (nargs != 1) /* internal error */ elog(ERROR, "moddatetime (%s): A single argument was expected", relname); args = trigger->tgargs; /* must be the field layout? */ tupdesc = rel->rd_att; /* * This gets the position in the tuple of the field we want. args[0] being * the name of the field to update, as passed in from the trigger. */ attnum = SPI_fnumber(tupdesc, args[0]); /* * This is where we check to see if the field we are supposed to update * even exists. The above function must return -1 if name not found? */ if (attnum < 0) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("\"%s\" has no attribute \"%s\"", relname, args[0]))); /* * Check the target field has an allowed type, and get the current * datetime as a value of that type. */ atttypid = SPI_gettypeid(tupdesc, attnum); if (atttypid == TIMESTAMPOID) newdt = DirectFunctionCall3(timestamp_in, CStringGetDatum("now"), ObjectIdGetDatum(InvalidOid), Int32GetDatum(-1)); else if (atttypid == TIMESTAMPTZOID) newdt = DirectFunctionCall3(timestamptz_in, CStringGetDatum("now"), ObjectIdGetDatum(InvalidOid), Int32GetDatum(-1)); else { ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("attribute \"%s\" of \"%s\" must be type TIMESTAMP or TIMESTAMPTZ", args[0], relname))); newdt = (Datum) 0; /* keep compiler quiet */ } /* 1 is the number of items in the arrays attnum and newdt. attnum is the positional number of the field to be updated. newdt is the new datetime stamp. NOTE that attnum and newdt are not arrays, but then a 1 element array is not an array any more then they are. Thus, they can be considered a one element array. */ rettuple = SPI_modifytuple(rel, rettuple, 1, &attnum, &newdt, NULL); if (rettuple == NULL) /* internal error */ elog(ERROR, "moddatetime (%s): %d returned by SPI_modifytuple", relname, SPI_result); /* Clean up */ pfree(relname); return PointerGetDatum(rettuple); }
Datum partition_insert_trigger(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; char *date_time; char child_table[sizeof(TABLE)+sizeof("2012_09_10")] = TABLE; int partition_field; Relation child_table_id; Oid child_table_oid; BulkInsertState bistate = GetBulkInsertState(); TupleTableSlot *slot; EState *estate = CreateExecutorState(); ResultRelInfo *resultRelInfo = makeNode(ResultRelInfo); List *recheckIndexes = NIL; /* make sure it's called as a trigger at all */ if (!CALLED_AS_TRIGGER(fcinfo)) elog(ERROR, "partition_insert_trigger: not called by trigger manager"); /* Sanity checks */ if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) || !TRIGGER_FIRED_BEFORE(trigdata->tg_event)) elog(ERROR, "partition_insert_trigger: not called on insert before"); #ifdef DEBUG elog(INFO, "Trigger Called for: %s", SPI_getrelname(trigdata->tg_relation)); #endif // Get the field number for the partition partition_field = SPI_fnumber(trigdata->tg_relation->rd_att, PARTITION_COLUMN); // Get the value for the partition_field date_time = SPI_getvalue(trigdata->tg_trigtuple, trigdata->tg_relation->rd_att, partition_field); //make sure date was specified if (!date_time) elog(ERROR, "You cannot insert data without specifying a value for the column %s", PARTITION_COLUMN); #ifdef DEBUG elog(INFO, "Trying to insert date_time=%s", date_time); #endif //add date_time, child_table_2012_01_23 strncpy(child_table + sizeof(TABLE) -1 , date_time, 4); //2012 child_table[sizeof(TABLE) + 3] = SEPARATOR; //2012_ strncpy(child_table + sizeof(TABLE) + 4 , date_time + 5, 2); //2012_01 child_table[sizeof(TABLE) + 6] = SEPARATOR; //2012_01_ strncpy(child_table + sizeof(TABLE) + 7 , date_time + 8, 2); //2012_01_23 #ifdef DEBUG elog(INFO, "New table will be %s", child_table); #endif pfree(date_time); //if you care about triggers on the child tables, call ExecBRInsertTriggers //don't care, continue //get the OID of the table we are looking for to insert child_table_oid = RelnameGetRelid(child_table); //Look for child child_table if (child_table_oid == InvalidOid){ elog(INFO, "partition_insert_trigger: Invalid child table %s, inserting data to main table %s", child_table, TABLE); return PointerGetDatum(trigdata->tg_trigtuple); } //get the descriptor of the table we are looking for child_table_id = RelationIdGetRelation(child_table_oid); //Get the child relation descriptor if (child_table_id == NULL){ elog(ERROR, "partition_insert_trigger: Failed to locate relation for child table %s, inserting data to main table %s", child_table, TABLE); return PointerGetDatum(trigdata->tg_trigtuple); } //set the resultRelInfo resultRelInfo->ri_RangeTableIndex = 1; /* dummy */ resultRelInfo->ri_RelationDesc = child_table_id; //setup the estate, not sure why estate->es_result_relations = resultRelInfo; estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; /* Set up a tuple slot not sure why yet */ slot = MakeSingleTupleTableSlot(trigdata->tg_relation->rd_att); ExecStoreTuple(trigdata->tg_trigtuple, slot, InvalidBuffer, false); //heap_insert(child_table_id, trigdata->tg_trigtuple, GetCurrentCommandId(true), use_wal, bistate); simple_heap_insert(child_table_id, trigdata->tg_trigtuple); if (resultRelInfo->ri_NumIndices > 0) recheckIndexes = ExecInsertIndexTuples(slot, &(trigdata->tg_trigtuple->t_self), estate); // not sure if this would work CatalogUpdateIndexes(child_table_id, trigdata->tg_trigtuple) //free the used memory list_free(recheckIndexes); ExecDropSingleTupleTableSlot(slot); //saw this somewhere :P RelationClose(child_table_id); //Must be called to free the relation memory page FreeBulkInsertState(bistate); // ? not sure if needed ? //If return next line will add to the regular table //return PointerGetDatum(trigdata->tg_trigtuple); //Return Null data, still have to figure out to return a proper X rows affected return PointerGetDatum(NULL); }
Datum insert_username(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger; /* to get trigger name */ int nargs; /* # of arguments */ Datum newval; /* new value of column */ char **args; /* arguments */ char *relname; /* triggered relation name */ Relation rel; /* triggered relation */ HeapTuple rettuple = NULL; TupleDesc tupdesc; /* tuple description */ int attnum; /* sanity checks from autoinc.c */ if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */ elog(ERROR, "insert_username: not fired by trigger manager"); if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event)) /* internal error */ elog(ERROR, "insert_username: can't process STATEMENT events"); if (TRIGGER_FIRED_AFTER(trigdata->tg_event)) /* internal error */ elog(ERROR, "insert_username: must be fired before event"); if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) rettuple = trigdata->tg_trigtuple; else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) rettuple = trigdata->tg_newtuple; else /* internal error */ elog(ERROR, "insert_username: can't process DELETE events"); rel = trigdata->tg_relation; relname = SPI_getrelname(rel); trigger = trigdata->tg_trigger; nargs = trigger->tgnargs; if (nargs != 1) /* internal error */ elog(ERROR, "insert_username (%s): one argument was expected", relname); args = trigger->tgargs; tupdesc = rel->rd_att; attnum = SPI_fnumber(tupdesc, args[0]); if (attnum < 0) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("\"%s\" has no attribute \"%s\"", relname, args[0]))); if (SPI_gettypeid(tupdesc, attnum) != TEXTOID) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("attribute \"%s\" of \"%s\" must be type TEXT", args[0], relname))); /* create fields containing name */ newval = DirectFunctionCall1(textin, CStringGetDatum(GetUserNameFromId(GetUserId()))); /* construct new tuple */ rettuple = SPI_modifytuple(rel, rettuple, 1, &attnum, &newval, NULL); if (rettuple == NULL) /* internal error */ elog(ERROR, "insert_username (\"%s\"): %d returned by SPI_modifytuple", relname, SPI_result); pfree(relname); return PointerGetDatum(rettuple); }
/***************************************************************************** * The entry point for the trigger function. * The Trigger takes a single SQL 'text' argument indicating the name of the * table the trigger was applied to. If this name is incorrect so will the * mirroring. ****************************************************************************/ Datum recordchange(PG_FUNCTION_ARGS) { TriggerData *trigdata; TupleDesc tupdesc; HeapTuple beforeTuple = NULL; HeapTuple afterTuple = NULL; HeapTuple retTuple = NULL; char *tblname; char op = 0; char *schemaname; char *fullyqualtblname; char *pkxpress = NULL; if (fcinfo->context != NULL) { if (SPI_connect() < 0) { ereport(ERROR, (errcode(ERRCODE_CONNECTION_FAILURE), errmsg("dbmirror:recordchange could not connect to SPI"))); return -1; } trigdata = (TriggerData *) fcinfo->context; /* Extract the table name */ tblname = SPI_getrelname(trigdata->tg_relation); #ifndef NOSCHEMAS schemaname = get_namespace_name(RelationGetNamespace(trigdata->tg_relation)); fullyqualtblname = SPI_palloc(strlen(tblname) + strlen(schemaname) + 6); sprintf(fullyqualtblname, "\"%s\".\"%s\"", schemaname, tblname); #else fullyqualtblname = SPI_palloc(strlen(tblname) + 3); sprintf(fullyqualtblname, "\"%s\"", tblname); #endif tupdesc = trigdata->tg_relation->rd_att; if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) { retTuple = trigdata->tg_newtuple; beforeTuple = trigdata->tg_trigtuple; afterTuple = trigdata->tg_newtuple; op = 'u'; } else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) { retTuple = trigdata->tg_trigtuple; afterTuple = trigdata->tg_trigtuple; op = 'i'; } else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) { retTuple = trigdata->tg_trigtuple; beforeTuple = trigdata->tg_trigtuple; op = 'd'; } else { ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("dbmirror:recordchange Unknown operation"))); } if (storePending(fullyqualtblname, beforeTuple, afterTuple, tupdesc, retTuple->t_tableOid, op)) { /* An error occoured. Skip the operation. */ ereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg("operation could not be mirrored"))); return PointerGetDatum(NULL); } debug_msg("dbmirror:recordchange returning on success"); SPI_pfree(fullyqualtblname); if (pkxpress != NULL) SPI_pfree(pkxpress); SPI_finish(); return PointerGetDatum(retTuple); } else { /* * Not being called as a trigger. */ return PointerGetDatum(NULL); } }