static int fetch_vertices_columns(SPITupleTable *tuptable, vertex_columns_t *vertex_columns) { vertex_columns->id = SPI_fnumber(SPI_tuptable->tupdesc, "id"); vertex_columns->x = SPI_fnumber(SPI_tuptable->tupdesc, "x"); vertex_columns->y = SPI_fnumber(SPI_tuptable->tupdesc, "y"); if (vertex_columns->id == SPI_ERROR_NOATTRIBUTE || vertex_columns->x == SPI_ERROR_NOATTRIBUTE || vertex_columns->y == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, query must return columns " "'id', 'x' and 'y'"); return -1; } if (SPI_gettypeid(SPI_tuptable->tupdesc, vertex_columns->id) != INT4OID || SPI_gettypeid(SPI_tuptable->tupdesc, vertex_columns->x) != FLOAT8OID || SPI_gettypeid(SPI_tuptable->tupdesc, vertex_columns->y) != FLOAT8OID) { elog(ERROR, "Error, column 'id' must be of type int4, 'x' and 'y' must be of type float8"); return -1; } return 0; }
static int ksp_fetch_edge_columns(SPITupleTable *tuptable, ksp_edge_columns_t *edge_columns, bool has_reverse_cost) { edge_columns->id = SPI_fnumber(SPI_tuptable->tupdesc, "id"); edge_columns->source = SPI_fnumber(SPI_tuptable->tupdesc, "source"); edge_columns->target = SPI_fnumber(SPI_tuptable->tupdesc, "target"); edge_columns->cost = SPI_fnumber(SPI_tuptable->tupdesc, "cost"); if (edge_columns->id == SPI_ERROR_NOATTRIBUTE || edge_columns->source == SPI_ERROR_NOATTRIBUTE || edge_columns->target == SPI_ERROR_NOATTRIBUTE || edge_columns->cost == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, query must return columns " "'id', 'source', 'target' and 'cost'"); return -1; } if (SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->source) != INT4OID || SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->target) != INT4OID || SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->cost) != FLOAT8OID) { elog(ERROR, "Error, columns 'source', 'target' must be of type int4, 'cost' must be of type float8"); return -1; } DBG("columns: id %i source %i target %i cost %f", edge_columns->id, edge_columns->source, edge_columns->target, edge_columns->cost); if (has_reverse_cost) { edge_columns->reverse_cost = SPI_fnumber(SPI_tuptable->tupdesc, "reverse_cost"); if (edge_columns->reverse_cost == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, reverse_cost is used, but query did't return " "'reverse_cost' column"); return -1; } if (SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->reverse_cost) != FLOAT8OID) { elog(ERROR, "Error, columns 'reverse_cost' must be of type float8"); return -1; } DBG("columns: reverse_cost cost %f", edge_columns->reverse_cost); } return 0; }
/* * Class: org_postgresql_pljava_internal_TupleDesc * Method: _getOid * Signature: (JI)Lorg/postgresql/pljava/internal/Oid; */ JNIEXPORT jobject JNICALL Java_org_postgresql_pljava_internal_TupleDesc__1getOid(JNIEnv* env, jclass cls, jlong _this, jint index) { jobject result = 0; BEGIN_NATIVE Ptr2Long p2l; p2l.longVal = _this; PG_TRY(); { Oid typeId = SPI_gettypeid((TupleDesc)p2l.ptrVal, (int)index); if(!OidIsValid(typeId)) { Exception_throw(ERRCODE_INVALID_DESCRIPTOR_INDEX, "Invalid attribute index \"%d\"", (int)index); } else { result = Oid_create(typeId); } } PG_CATCH(); { Exception_throw_ERROR("SPI_gettypeid"); } PG_END_TRY(); END_NATIVE return result; }
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); }
Datum log_entries(PG_FUNCTION_ARGS) { Relation rel; TriggerData *trigger_data; Trigger *trigger; TupleDesc tupdesc; Datum current_user; HeapTuple rettuple; Datum newvals[MaxAttributes]; int chattr[MaxAttributes]; if (!CALLED_AS_TRIGGER(fcinfo)) elog(ERROR, "log_entries should be called only as trigger"); trigger_data = (TriggerData *) fcinfo->context; if (!TRIGGER_FIRED_FOR_ROW(trigger_data->tg_event)) elog(ERROR, "log_entries should be fire only for row"); if (TRIGGER_FIRED_BY_DELETE(trigger_data->tg_event)) return (Datum) trigger_data->tg_trigtuple; trigger = trigger_data->tg_trigger; if (trigger->tgnargs != MaxAttributes) elog(ERROR, "log_entries, need two arguments"); rel = trigger_data->tg_relation; tupdesc = rel->rd_att; if (SPI_gettypeid(tupdesc, SPI_fnumber(tupdesc, trigger->tgargs[0])) != ABSTIMEOID) elog(ERROR, "log_entries, first argument should be ABSTIME"); if (SPI_gettypeid(tupdesc, SPI_fnumber(tupdesc, trigger->tgargs[1])) != TEXTOID) elog(ERROR, "log_entries, first argument should be TEXT"); current_user = CStringGetTextDatum(GetUserNameFromId(GetUserId(), false)); newvals[0] = GetCurrentAbsoluteTime(); newvals[1] = current_user; chattr[0] = SPI_fnumber(tupdesc, trigger->tgargs[0]); chattr[1] = SPI_fnumber(tupdesc, trigger->tgargs[1]); rettuple = SPI_modifytuple(rel, trigger_data->tg_trigtuple, MaxAttributes, chattr, newvals, NULL); return PointerGetDatum(rettuple); }
Type TupleDesc_getColumnType(TupleDesc tupleDesc, int index) { Type type; Oid typeId = SPI_gettypeid(tupleDesc, index); if(!OidIsValid(typeId)) { Exception_throw(ERRCODE_INVALID_DESCRIPTOR_INDEX, "Invalid attribute index \"%d\"", (int)index); type = 0; } else type = Type_objectTypeFromOid(typeId, Invocation_getTypeMap()); return type; }
/* * This function fetches the resturction columns from an SPITupleTable.. * */ static int fetch_restrict_columns(SPITupleTable *tuptable, restrict_columns_t *restrict_columns) { restrict_columns->target_id = SPI_fnumber(SPI_tuptable->tupdesc, "target_id"); restrict_columns->via_path = SPI_fnumber(SPI_tuptable->tupdesc, "via_path"); restrict_columns->to_cost = SPI_fnumber(SPI_tuptable->tupdesc, "to_cost"); if (restrict_columns->target_id == SPI_ERROR_NOATTRIBUTE || restrict_columns->via_path == SPI_ERROR_NOATTRIBUTE || restrict_columns->to_cost == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, restriction query must return columns " "'target_id', 'via_path' and 'to_cost'"); return -1; } if (SPI_gettypeid(SPI_tuptable->tupdesc, restrict_columns->target_id) != INT4OID || SPI_gettypeid(SPI_tuptable->tupdesc, restrict_columns->via_path) != TEXTOID || SPI_gettypeid(SPI_tuptable->tupdesc, restrict_columns->to_cost) != FLOAT8OID) { elog(ERROR, "Error, restriction columns 'target_id' must be of type int4, 'via_path' must be of type text, 'to_cost' must be of type float8"); return -1; } return 0; }
/* * Class: org_postgresql_pljava_internal_TupleDesc * Method: _formTuple * Signature: (J[Ljava/lang/Object;)Lorg/postgresql/pljava/internal/Tuple; */ JNIEXPORT jobject JNICALL Java_org_postgresql_pljava_internal_TupleDesc__1formTuple(JNIEnv* env, jclass cls, jlong _this, jobjectArray jvalues) { jobject result = 0; BEGIN_NATIVE Ptr2Long p2l; p2l.longVal = _this; PG_TRY(); { jint idx; HeapTuple tuple; MemoryContext curr; TupleDesc self = (TupleDesc)p2l.ptrVal; int count = self->natts; Datum* values = (Datum*)palloc(count * sizeof(Datum)); char* nulls = palloc(count); jobject typeMap = Invocation_getTypeMap(); memset(values, 0, count * sizeof(Datum)); memset(nulls, 'n', count); /* all values null initially */ for(idx = 0; idx < count; ++idx) { jobject value = JNI_getObjectArrayElement(jvalues, idx); if(value != 0) { Type type = Type_fromOid(SPI_gettypeid(self, idx + 1), typeMap); values[idx] = Type_coerceObject(type, value); nulls[idx] = ' '; } } curr = MemoryContextSwitchTo(JavaMemoryContext); tuple = heap_formtuple(self, values, nulls); result = Tuple_internalCreate(tuple, false); MemoryContextSwitchTo(curr); pfree(values); pfree(nulls); } PG_CATCH(); { Exception_throw_ERROR("heap_formtuple"); } PG_END_TRY(); END_NATIVE return result; }
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); /* special handling for EV_WHEN */ if (i == EV_WHEN) { bool isnull; Oid oid = SPI_gettypeid(SPI_tuptable->tupdesc, 1); Datum res; if (oid != BOOLOID) elog(ERROR, "when= query result must be boolean, got=%u", oid); res = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull); if (isnull) elog(ERROR, "when= should not be NULL"); if (DatumGetBool(res) == 0) ev->skip_event = true; continue; } /* normal field */ 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); } } }
static bool fetch_column_info( Column_info_t *info) { PGR_DBG("Fetching column info of %s", info->name); info->colNumber = SPI_fnumber(SPI_tuptable->tupdesc, info->name); if (info->strict && !column_found(info->colNumber)) { elog(ERROR, "Column '%s' not Found", info->name); } if (column_found(info->colNumber)) { (info->type) = SPI_gettypeid(SPI_tuptable->tupdesc, (info->colNumber)); if (SPI_result == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Type of column '%s' not Found", info->name); } PGR_DBG("Column %s found: %llu", info->name, info->type); return true; } PGR_DBG("Column %s not found", info->name); return false; }
/* * We have to assign TupleDesc to all used record variables step by step. We * would to use a exec routines for query preprocessing, so we must to create * a typed NULL value, and this value is assigned to record variable. */ void plpgsql_check_assign_tupdesc_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec, TupleDesc tupdesc, bool isnull) { if (tupdesc == NULL) { plpgsql_check_put_error(cstate, 0, 0, "tuple descriptor is empty", NULL, NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); return; } /* * row variable has assigned TupleDesc already, so don't be processed here */ if (rec != NULL) { PLpgSQL_rec *target = (PLpgSQL_rec *) (cstate->estate->datums[rec->dno]); plpgsql_check_recval_release(target); plpgsql_check_recval_assign_tupdesc(cstate, target, tupdesc, isnull); } else if (row != NULL && tupdesc != NULL) { int td_natts = tupdesc->natts; int fnum; int anum; anum = 0; for (fnum = 0; fnum < row->nfields; fnum++) { if (row->varnos[fnum] < 0) continue; /* skip dropped column in row struct */ while (anum < td_natts && TupleDescAttr(tupdesc, anum)->attisdropped) anum++; /* skip dropped column in tuple */ if (anum < td_natts) { Oid valtype = SPI_gettypeid(tupdesc, anum + 1); PLpgSQL_datum *target = cstate->estate->datums[row->varnos[fnum]]; switch (target->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) target; plpgsql_check_assign_to_target_type(cstate, var->datatype->typoid, var->datatype->atttypmod, valtype, isnull); } break; case PLPGSQL_DTYPE_RECFIELD: { Oid expected_typoid; int expected_typmod; plpgsql_check_target(cstate, target->dno, &expected_typoid, &expected_typmod); plpgsql_check_assign_to_target_type(cstate, expected_typoid, expected_typmod, valtype, isnull); } break; default: ; /* nope */ } anum++; } } } }
Datum tsquery_rewrite(PG_FUNCTION_ARGS) { QUERYTYPE *query = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0))); text *in = PG_GETARG_TEXT_P(1); QUERYTYPE *rewrited = query; QTNode *tree; char *buf; void *plan; Portal portal; bool isnull; int i; if (query->size == 0) { PG_FREE_IF_COPY(in, 1); PG_RETURN_POINTER(rewrited); } tree = QT2QTN(GETQUERY(query), GETOPERAND(query)); QTNTernary(tree); QTNSort(tree); buf = (char *) palloc(VARSIZE(in)); memcpy(buf, VARDATA(in), VARSIZE(in) - VARHDRSZ); buf[VARSIZE(in) - VARHDRSZ] = '\0'; SPI_connect(); if (tsqOid == InvalidOid) get_tsq_Oid(); if ((plan = SPI_prepare(buf, 0, NULL)) == NULL) elog(ERROR, "SPI_prepare('%s') returns NULL", buf); if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, false)) == NULL) elog(ERROR, "SPI_cursor_open('%s') returns NULL", buf); SPI_cursor_fetch(portal, true, 100); if (SPI_tuptable->tupdesc->natts != 2) elog(ERROR, "number of fields doesn't equal to 2"); if (SPI_gettypeid(SPI_tuptable->tupdesc, 1) != tsqOid) elog(ERROR, "column #1 isn't of tsquery type"); if (SPI_gettypeid(SPI_tuptable->tupdesc, 2) != tsqOid) elog(ERROR, "column #2 isn't of tsquery type"); while (SPI_processed > 0 && tree) { for (i = 0; i < SPI_processed && tree; i++) { Datum qdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull); Datum sdata; if (isnull) continue; sdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &isnull); if (!isnull) { QUERYTYPE *qtex = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM(qdata)); QUERYTYPE *qtsubs = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM(sdata)); QTNode *qex, *qsubs = NULL; if (qtex->size == 0) { if (qtex != (QUERYTYPE *) DatumGetPointer(qdata)) pfree(qtex); if (qtsubs != (QUERYTYPE *) DatumGetPointer(sdata)) pfree(qtsubs); continue; } qex = QT2QTN(GETQUERY(qtex), GETOPERAND(qtex)); QTNTernary(qex); QTNSort(qex); if (qtsubs->size) qsubs = QT2QTN(GETQUERY(qtsubs), GETOPERAND(qtsubs)); tree = findsubquery(tree, qex, SPIMemory, qsubs, NULL); QTNFree(qex); if (qtex != (QUERYTYPE *) DatumGetPointer(qdata)) pfree(qtex); QTNFree(qsubs); if (qtsubs != (QUERYTYPE *) DatumGetPointer(sdata)) pfree(qtsubs); } } SPI_freetuptable(SPI_tuptable); SPI_cursor_fetch(portal, true, 100); } SPI_freetuptable(SPI_tuptable); SPI_cursor_close(portal); SPI_freeplan(plan); SPI_finish(); if (tree) { QTNBinary(tree); rewrited = QTN2QT(tree, PlainMemory); QTNFree(tree); PG_FREE_IF_COPY(query, 0); } else { rewrited->len = HDRSIZEQT; rewrited->size = 0; } pfree(buf); PG_FREE_IF_COPY(in, 1); PG_RETURN_POINTER(rewrited); }
/** * Gets the raw data from the database and populates the edgeColumns */ static bool fetchEdgeTspColumns(SPITupleTable *tuptable, tspEdgeType *edgeColumns,bool reverseCost) { edgeColumns->source = SPI_fnumber(SPI_tuptable->tupdesc, "source"); edgeColumns->target = SPI_fnumber(SPI_tuptable->tupdesc, "target"); edgeColumns->cost = SPI_fnumber(SPI_tuptable->tupdesc, "cost"); if(reverseCost){ edgeColumns->reverseCost = SPI_fnumber(SPI_tuptable->tupdesc, "reverse_cost"); } if ( edgeColumns->source == SPI_ERROR_NOATTRIBUTE ){ elog(ERROR, "Error, query must include 'source' column "); return false; } if( edgeColumns->target == SPI_ERROR_NOATTRIBUTE ){ elog(ERROR, "Error, query must include 'target' column "); return false; } if( edgeColumns->cost == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, query must include 'cost' column "); return false; } if( reverseCost && edgeColumns->reverseCost == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, query must include 'reverse_cost' column"); return false; } if (SPI_gettypeid(SPI_tuptable->tupdesc, edgeColumns->source) != INT4OID || SPI_gettypeid(SPI_tuptable->tupdesc, edgeColumns->target) != INT4OID ){ elog(ERROR, "Error, columns 'source', 'target' must be of type int4 "); return false; } int type= SPI_gettypeid(SPI_tuptable->tupdesc, edgeColumns->cost); switch ( type){ case INT2OID: case INT4OID: case FLOAT4OID: case FLOAT8OID: case TIDOID: break; default: elog(ERROR, "Unknown type for 'cost' column %d",type); return false; } if(reverseCost ){ int type= SPI_gettypeid(SPI_tuptable->tupdesc, edgeColumns->cost); switch ( type){ case INT2OID: case INT4OID: case FLOAT4OID: case FLOAT8OID: case TIDOID: break; default: elog(ERROR, "Unknown type for 'reverse_cost' column %d",type); return false; } } if(reverseCost){ DBG("columns: source %i target %i cost %.4f reverse_cost %.4f", edgeColumns->source, edgeColumns->target, edgeColumns->cost,edgeColumns->reverseCost); } else { DBG("columns: source %i target %i cost %.4f", edgeColumns->source, edgeColumns->target, edgeColumns->cost); } return true; }
/** * @fn Datum reorg_apply(PG_FUNCTION_ARGS) * @brief Apply operations in log table into temp table. * * reorg_apply(sql_peek, sql_insert, sql_delete, sql_update, sql_pop, count) * * @param sql_peek SQL to pop tuple from log table. * @param sql_insert SQL to insert into temp table. * @param sql_delete SQL to delete from temp table. * @param sql_update SQL to update temp table. * @param sql_pop SQL to delete tuple from log table. * @param count Max number of operations, or no count iff <=0. * @retval Number of performed operations. */ Datum reorg_apply(PG_FUNCTION_ARGS) { #define DEFAULT_PEEK_COUNT 1000 const char *sql_peek = PG_GETARG_CSTRING(0); const char *sql_insert = PG_GETARG_CSTRING(1); const char *sql_delete = PG_GETARG_CSTRING(2); const char *sql_update = PG_GETARG_CSTRING(3); const char *sql_pop = PG_GETARG_CSTRING(4); int32 count = PG_GETARG_INT32(5); SPIPlanPtr plan_peek = NULL; SPIPlanPtr plan_insert = NULL; SPIPlanPtr plan_delete = NULL; SPIPlanPtr plan_update = NULL; SPIPlanPtr plan_pop = NULL; uint32 n, i; Oid argtypes_peek[1] = { INT4OID }; Datum values_peek[1]; bool nulls_peek[1] = { 0 }; /* authority check */ must_be_superuser("reorg_apply"); /* connect to SPI manager */ reorg_init(); /* peek tuple in log */ plan_peek = reorg_prepare(sql_peek, 1, argtypes_peek); for (n = 0;;) { int ntuples; SPITupleTable *tuptable; TupleDesc desc; Oid argtypes[3]; /* id, pk, row */ Datum values[3]; /* id, pk, row */ bool nulls[3]; /* id, pk, row */ /* peek tuple in log */ if (count == 0) values_peek[0] = Int32GetDatum(DEFAULT_PEEK_COUNT); else values_peek[0] = Int32GetDatum(Min(count - n, DEFAULT_PEEK_COUNT)); execute_plan(SPI_OK_SELECT, plan_peek, values_peek, nulls_peek); if (SPI_processed <= 0) break; /* copy tuptable because we will call other sqls. */ ntuples = SPI_processed; tuptable = SPI_tuptable; desc = tuptable->tupdesc; argtypes[0] = SPI_gettypeid(desc, 1); /* id */ argtypes[1] = SPI_gettypeid(desc, 2); /* pk */ argtypes[2] = SPI_gettypeid(desc, 3); /* row */ for (i = 0; i < ntuples; i++, n++) { HeapTuple tuple; tuple = tuptable->vals[i]; values[0] = SPI_getbinval(tuple, desc, 1, &nulls[0]); values[1] = SPI_getbinval(tuple, desc, 2, &nulls[1]); values[2] = SPI_getbinval(tuple, desc, 3, &nulls[2]); if (nulls[1]) { /* INSERT */ if (plan_insert == NULL) plan_insert = reorg_prepare(sql_insert, 1, &argtypes[2]); execute_plan(SPI_OK_INSERT, plan_insert, &values[2], &nulls[2]); } else if (nulls[2]) { /* DELETE */ if (plan_delete == NULL) plan_delete = reorg_prepare(sql_delete, 1, &argtypes[1]); execute_plan(SPI_OK_DELETE, plan_delete, &values[1], &nulls[1]); } else { /* UPDATE */ if (plan_update == NULL) plan_update = reorg_prepare(sql_update, 2, &argtypes[1]); execute_plan(SPI_OK_UPDATE, plan_update, &values[1], &nulls[1]); } } /* delete tuple in log */ if (plan_pop == NULL) plan_pop = reorg_prepare(sql_pop, 1, argtypes); execute_plan(SPI_OK_DELETE, plan_pop, values, nulls); SPI_freetuptable(tuptable); } SPI_finish(); PG_RETURN_INT32(n); }
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); }
static int fetch_edge_shooting_star_columns(SPITupleTable *tuptable, edge_shooting_star_columns_t *edge_columns, bool has_reverse_cost) { edge_columns->id = SPI_fnumber(SPI_tuptable->tupdesc, "id"); edge_columns->source = SPI_fnumber(SPI_tuptable->tupdesc, "source"); edge_columns->target = SPI_fnumber(SPI_tuptable->tupdesc, "target"); edge_columns->cost = SPI_fnumber(SPI_tuptable->tupdesc, "cost"); if (edge_columns->id == SPI_ERROR_NOATTRIBUTE || edge_columns->source == SPI_ERROR_NOATTRIBUTE || edge_columns->target == SPI_ERROR_NOATTRIBUTE || edge_columns->cost == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, query must return columns " "'id', 'source', 'target' and 'cost'"); return -1; } if (SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->source) != INT4OID || SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->target) != INT4OID || SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->cost) != FLOAT8OID) { elog(ERROR, "Error, columns 'source', 'target' must be of type int4, " "'cost' must be of type float8"); return -1; } DBG("columns: id %i source %i target %i cost %i", edge_columns->id, edge_columns->source, edge_columns->target, edge_columns->cost); if (has_reverse_cost) { edge_columns->reverse_cost = SPI_fnumber(SPI_tuptable->tupdesc, "reverse_cost"); if (edge_columns->reverse_cost == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, reverse_cost is used, but query did't return " "'reverse_cost' column"); return -1; } if (SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->reverse_cost) != FLOAT8OID) { elog(ERROR, "Error, columns 'reverse_cost' must be of type float8"); return -1; } DBG("columns: reverse_cost cost %i", edge_columns->reverse_cost); } edge_columns->s_x = SPI_fnumber(SPI_tuptable->tupdesc, "x1"); edge_columns->s_y = SPI_fnumber(SPI_tuptable->tupdesc, "y1"); edge_columns->t_x = SPI_fnumber(SPI_tuptable->tupdesc, "x2"); edge_columns->t_y = SPI_fnumber(SPI_tuptable->tupdesc, "y2"); if (edge_columns->s_x == SPI_ERROR_NOATTRIBUTE || edge_columns->s_y == SPI_ERROR_NOATTRIBUTE || edge_columns->t_x == SPI_ERROR_NOATTRIBUTE || edge_columns->t_y == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, query must return columns " "'x1', 'x2', 'y1' and 'y2'"); return -1; } DBG("columns: x1 %i y1 %i x2 %i y2 %i", edge_columns->s_x, edge_columns->s_y, edge_columns->t_x,edge_columns->t_y); edge_columns->to_cost = SPI_fnumber(SPI_tuptable->tupdesc, "to_cost"); edge_columns->rule = SPI_fnumber(SPI_tuptable->tupdesc, "rule"); if (edge_columns->to_cost == SPI_ERROR_NOATTRIBUTE || edge_columns->rule == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, query must return columns " "'to_cost' and 'rule'"); return -1; } return 0; }
/** * @fn Datum repack_apply(PG_FUNCTION_ARGS) * @brief Apply operations in log table into temp table. * * repack_apply(sql_peek, sql_insert, sql_delete, sql_update, sql_pop, count) * * @param sql_peek SQL to pop tuple from log table. * @param sql_insert SQL to insert into temp table. * @param sql_delete SQL to delete from temp table. * @param sql_update SQL to update temp table. * @param sql_pop SQL to bulk-delete tuples from log table. * @param count Max number of operations, or no count iff <=0. * @retval Number of performed operations. */ Datum repack_apply(PG_FUNCTION_ARGS) { #define DEFAULT_PEEK_COUNT 1000 const char *sql_peek = PG_GETARG_CSTRING(0); const char *sql_insert = PG_GETARG_CSTRING(1); const char *sql_delete = PG_GETARG_CSTRING(2); const char *sql_update = PG_GETARG_CSTRING(3); /* sql_pop, the fourth arg, will be used in the loop below */ int32 count = PG_GETARG_INT32(5); SPIPlanPtr plan_peek = NULL; SPIPlanPtr plan_insert = NULL; SPIPlanPtr plan_delete = NULL; SPIPlanPtr plan_update = NULL; uint32 n, i; Oid argtypes_peek[1] = { INT4OID }; Datum values_peek[1]; const char nulls_peek[1] = { 0 }; StringInfoData sql_pop; initStringInfo(&sql_pop); /* authority check */ must_be_superuser("repack_apply"); /* connect to SPI manager */ repack_init(); /* peek tuple in log */ plan_peek = repack_prepare(sql_peek, 1, argtypes_peek); for (n = 0;;) { int ntuples; SPITupleTable *tuptable; TupleDesc desc; Oid argtypes[3]; /* id, pk, row */ Datum values[3]; /* id, pk, row */ bool nulls[3]; /* id, pk, row */ /* peek tuple in log */ if (count <= 0) values_peek[0] = Int32GetDatum(DEFAULT_PEEK_COUNT); else values_peek[0] = Int32GetDatum(Min(count - n, DEFAULT_PEEK_COUNT)); execute_plan(SPI_OK_SELECT, plan_peek, values_peek, nulls_peek); if (SPI_processed <= 0) break; /* copy tuptable because we will call other sqls. */ ntuples = SPI_processed; tuptable = SPI_tuptable; desc = tuptable->tupdesc; argtypes[0] = SPI_gettypeid(desc, 1); /* id */ argtypes[1] = SPI_gettypeid(desc, 2); /* pk */ argtypes[2] = SPI_gettypeid(desc, 3); /* row */ resetStringInfo(&sql_pop); appendStringInfoString(&sql_pop, PG_GETARG_CSTRING(4)); for (i = 0; i < ntuples; i++, n++) { HeapTuple tuple; char *pkid; tuple = tuptable->vals[i]; values[0] = SPI_getbinval(tuple, desc, 1, &nulls[0]); values[1] = SPI_getbinval(tuple, desc, 2, &nulls[1]); values[2] = SPI_getbinval(tuple, desc, 3, &nulls[2]); pkid = SPI_getvalue(tuple, desc, 1); Assert(pkid != NULL); if (nulls[1]) { /* INSERT */ if (plan_insert == NULL) plan_insert = repack_prepare(sql_insert, 1, &argtypes[2]); execute_plan(SPI_OK_INSERT, plan_insert, &values[2], (nulls[2] ? "n" : " ")); } else if (nulls[2]) { /* DELETE */ if (plan_delete == NULL) plan_delete = repack_prepare(sql_delete, 1, &argtypes[1]); execute_plan(SPI_OK_DELETE, plan_delete, &values[1], (nulls[1] ? "n" : " ")); } else { /* UPDATE */ if (plan_update == NULL) plan_update = repack_prepare(sql_update, 2, &argtypes[1]); execute_plan(SPI_OK_UPDATE, plan_update, &values[1], (nulls[1] ? "n" : " ")); } /* Add the primary key ID of each row from the log * table we have processed so far to this * DELETE ... IN (...) query string, so we * can delete all the rows we have processed at-once. */ if (i == 0) appendStringInfoString(&sql_pop, pkid); else appendStringInfo(&sql_pop, ",%s", pkid); pfree(pkid); } /* i must be > 0 (and hence we must have some rows to delete) * since SPI_processed > 0 */ Assert(i > 0); appendStringInfoString(&sql_pop, ");"); /* Bulk delete of processed rows from the log table */ execute(SPI_OK_DELETE, sql_pop.data); SPI_freetuptable(tuptable); } SPI_finish(); PG_RETURN_INT32(n); }
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 later use. */ if (SPI_keepplan(pplan)) /* internal error */ elog(ERROR, "check_primary_key: SPI_keepplan failed"); 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 tsquery_rewrite_query(PG_FUNCTION_ARGS) { TSQuery query = PG_GETARG_TSQUERY_COPY(0); text *in = PG_GETARG_TEXT_P(1); TSQuery rewrited = query; MemoryContext outercontext = CurrentMemoryContext; MemoryContext oldcontext; QTNode *tree; char *buf; SPIPlanPtr plan; Portal portal; bool isnull; if (query->size == 0) { PG_FREE_IF_COPY(in, 1); PG_RETURN_POINTER(rewrited); } tree = QT2QTN(GETQUERY(query), GETOPERAND(query)); QTNTernary(tree); QTNSort(tree); buf = text_to_cstring(in); SPI_connect(); if ((plan = SPI_prepare(buf, 0, NULL)) == NULL) elog(ERROR, "SPI_prepare(\"%s\") failed", buf); if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL) elog(ERROR, "SPI_cursor_open(\"%s\") failed", buf); SPI_cursor_fetch(portal, true, 100); if (SPI_tuptable == NULL || SPI_tuptable->tupdesc->natts != 2 || SPI_gettypeid(SPI_tuptable->tupdesc, 1) != TSQUERYOID || SPI_gettypeid(SPI_tuptable->tupdesc, 2) != TSQUERYOID) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("ts_rewrite query must return two tsquery columns"))); while (SPI_processed > 0 && tree) { uint64 i; for (i = 0; i < SPI_processed && tree; i++) { Datum qdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull); Datum sdata; if (isnull) continue; sdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &isnull); if (!isnull) { TSQuery qtex = DatumGetTSQuery(qdata); TSQuery qtsubs = DatumGetTSQuery(sdata); QTNode *qex, *qsubs = NULL; if (qtex->size == 0) { if (qtex != (TSQuery) DatumGetPointer(qdata)) pfree(qtex); if (qtsubs != (TSQuery) DatumGetPointer(sdata)) pfree(qtsubs); continue; } qex = QT2QTN(GETQUERY(qtex), GETOPERAND(qtex)); QTNTernary(qex); QTNSort(qex); if (qtsubs->size) qsubs = QT2QTN(GETQUERY(qtsubs), GETOPERAND(qtsubs)); oldcontext = MemoryContextSwitchTo(outercontext); tree = findsubquery(tree, qex, qsubs, NULL); MemoryContextSwitchTo(oldcontext); QTNFree(qex); if (qtex != (TSQuery) DatumGetPointer(qdata)) pfree(qtex); QTNFree(qsubs); if (qtsubs != (TSQuery) DatumGetPointer(sdata)) pfree(qtsubs); if (tree) { /* ready the tree for another pass */ QTNClearFlags(tree, QTN_NOCHANGE); QTNSort(tree); } } } SPI_freetuptable(SPI_tuptable); SPI_cursor_fetch(portal, true, 100); } SPI_freetuptable(SPI_tuptable); SPI_cursor_close(portal); SPI_freeplan(plan); SPI_finish(); if (tree) { QTNBinary(tree); rewrited = QTN2QT(tree); QTNFree(tree); PG_FREE_IF_COPY(query, 0); } else { SET_VARSIZE(rewrited, HDRSIZETQ); rewrited->size = 0; } pfree(buf); PG_FREE_IF_COPY(in, 1); PG_RETURN_POINTER(rewrited); }
Datum _Slony_I_logTrigger(PG_FUNCTION_ARGS) { TransactionId newXid = GetTopTransactionId(); Slony_I_ClusterStatus *cs; TriggerData *tg; Datum argv[4]; text *cmdtype = NULL; int rc; Name cluster_name; int32 tab_id; char *attkind; int attkind_idx; int cmddata_need; /* * Don't do any logging if the current session role isn't Origin. */ if (SessionReplicationRole != SESSION_REPLICATION_ROLE_ORIGIN) return PointerGetDatum(NULL); /* * Get the trigger call context */ if (!CALLED_AS_TRIGGER(fcinfo)) elog(ERROR, "Slony-I: logTrigger() not called as trigger"); tg = (TriggerData *) (fcinfo->context); /* * Check all logTrigger() calling conventions */ if (!TRIGGER_FIRED_AFTER(tg->tg_event)) elog(ERROR, "Slony-I: logTrigger() must be fired AFTER"); if (!TRIGGER_FIRED_FOR_ROW(tg->tg_event)) elog(ERROR, "Slony-I: logTrigger() must be fired FOR EACH ROW"); if (tg->tg_trigger->tgnargs != 3) elog(ERROR, "Slony-I: logTrigger() must be defined with 3 args"); /* * Connect to the SPI manager */ if ((rc = SPI_connect()) < 0) elog(ERROR, "Slony-I: SPI_connect() failed in createEvent()"); /* * Get all the trigger arguments */ cluster_name = DatumGetName(DirectFunctionCall1(namein, CStringGetDatum(tg->tg_trigger->tgargs[0]))); tab_id = strtol(tg->tg_trigger->tgargs[1], NULL, 10); attkind = tg->tg_trigger->tgargs[2]; /* * Get or create the cluster status information and make sure it has the * SPI plans that we need here. */ cs = getClusterStatus(cluster_name, PLAN_INSERT_LOG); /* * Do the following only once per transaction. */ if (!TransactionIdEquals(cs->currentXid, newXid)) { int32 log_status; bool isnull; /* * Determine the currently active log table */ if (SPI_execp(cs->plan_get_logstatus, NULL, NULL, 0) < 0) elog(ERROR, "Slony-I: cannot determine log status"); if (SPI_processed != 1) elog(ERROR, "Slony-I: cannot determine log status"); log_status = DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull)); SPI_freetuptable(SPI_tuptable); switch (log_status) { case 0: case 2: cs->plan_active_log = cs->plan_insert_log_1; break; case 1: case 3: cs->plan_active_log = cs->plan_insert_log_2; break; default: elog(ERROR, "Slony-I: illegal log status %d", log_status); break; } cs->currentXid = newXid; } /* * Determine cmdtype and cmddata depending on the command type */ if (TRIGGER_FIRED_BY_INSERT(tg->tg_event)) { HeapTuple new_row = tg->tg_trigtuple; TupleDesc tupdesc = tg->tg_relation->rd_att; char *col_ident; char *col_value; int len_ident; int len_value; int i; int need_comma = false; char *OldDateStyle; char *cp = VARDATA(cs->cmddata_buf); /* * INSERT * * cmdtype = 'I' cmddata = ("col" [, ...]) values ('value' [, ...]) */ cmdtype = cs->cmdtype_I; /* * Specify all the columns */ *cp++ = '('; for (i = 0; i < tg->tg_relation->rd_att->natts; i++) { /* * Skip dropped columns */ if (tupdesc->attrs[i]->attisdropped) continue; col_ident = (char *) slon_quote_identifier(SPI_fname(tupdesc, i + 1)); cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 + (len_ident = strlen(col_ident)); if (cs->cmddata_size < cmddata_need) { int have = (cp - (char *) (cs->cmddata_buf)); while (cs->cmddata_size < cmddata_need) cs->cmddata_size *= 2; cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size); cp = (char *) (cs->cmddata_buf) + have; } if (need_comma) *cp++ = ','; else need_comma = true; memcpy(cp, col_ident, len_ident); cp += len_ident; } /* * Append the string ") values (" */ *cp++ = ')'; *cp++ = ' '; *cp++ = 'v'; *cp++ = 'a'; *cp++ = 'l'; *cp++ = 'u'; *cp++ = 'e'; *cp++ = 's'; *cp++ = ' '; *cp++ = '('; /* * Append the values */ need_comma = false; OldDateStyle = GetConfigOptionByName("DateStyle", NULL); if (!strstr(OldDateStyle, "ISO")) set_config_option("DateStyle", "ISO", PGC_USERSET, PGC_S_SESSION, true, true); for (i = 0; i < tg->tg_relation->rd_att->natts; i++) { /* * Skip dropped columns */ if (tupdesc->attrs[i]->attisdropped) continue; if ((col_value = SPI_getvalue(new_row, tupdesc, i + 1)) == NULL) { col_value = "NULL"; } else { col_value = slon_quote_literal(col_value); } cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 + (len_value = strlen(col_value)); if (cs->cmddata_size < cmddata_need) { int have = (cp - (char *) (cs->cmddata_buf)); while (cs->cmddata_size < cmddata_need) cs->cmddata_size *= 2; cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size); cp = (char *) (cs->cmddata_buf) + have; } if (need_comma) *cp++ = ','; else need_comma = true; memcpy(cp, col_value, len_value); cp += len_value; } if (!strstr(OldDateStyle, "ISO")) set_config_option("DateStyle", OldDateStyle, PGC_USERSET, PGC_S_SESSION, true, true); /* * Terminate and done */ *cp++ = ')'; *cp = '\0'; SET_VARSIZE(cs->cmddata_buf, VARHDRSZ + (cp - VARDATA(cs->cmddata_buf))); } else if (TRIGGER_FIRED_BY_UPDATE(tg->tg_event)) { HeapTuple old_row = tg->tg_trigtuple; HeapTuple new_row = tg->tg_newtuple; TupleDesc tupdesc = tg->tg_relation->rd_att; Datum old_value; Datum new_value; bool old_isnull; bool new_isnull; char *col_ident; char *col_value; int len_ident; int len_value; int i; int need_comma = false; int need_and = false; char *OldDateStyle; char *cp = VARDATA(cs->cmddata_buf); /* * UPDATE * * cmdtype = 'U' cmddata = "col_ident"='value' [, ...] where * "pk_ident" = 'value' [ and ...] */ cmdtype = cs->cmdtype_U; for (i = 0; i < tg->tg_relation->rd_att->natts; i++) { /* * Ignore dropped columns */ if (tupdesc->attrs[i]->attisdropped) continue; old_value = SPI_getbinval(old_row, tupdesc, i + 1, &old_isnull); new_value = SPI_getbinval(new_row, tupdesc, i + 1, &new_isnull); /* * If old and new value are NULL, the column is unchanged */ if (old_isnull && new_isnull) continue; /* * If both are NOT NULL, we need to compare the values and skip * setting the column if equal */ if (!old_isnull && !new_isnull) { Oid opr_oid; FmgrInfo *opr_finfo_p; /* * Lookup the equal operators function call info using the * typecache if available */ #ifdef HAVE_TYPCACHE TypeCacheEntry *type_cache; type_cache = lookup_type_cache( SPI_gettypeid(tupdesc, i + 1), TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO); opr_oid = type_cache->eq_opr; if (opr_oid == ARRAY_EQ_OP) opr_oid = InvalidOid; else opr_finfo_p = &(type_cache->eq_opr_finfo); #else FmgrInfo opr_finfo; opr_oid = compatible_oper_funcid(makeList1(makeString("=")), SPI_gettypeid(tupdesc, i + 1), SPI_gettypeid(tupdesc, i + 1), true); if (OidIsValid(opr_oid)) { fmgr_info(opr_oid, &opr_finfo); opr_finfo_p = &opr_finfo; } #endif /* * If we have an equal operator, use that to do binary * comparision. Else get the string representation of both * attributes and do string comparision. */ if (OidIsValid(opr_oid)) { if (DatumGetBool(FunctionCall2(opr_finfo_p, old_value, new_value))) continue; } else { char *old_strval = SPI_getvalue(old_row, tupdesc, i + 1); char *new_strval = SPI_getvalue(new_row, tupdesc, i + 1); if (strcmp(old_strval, new_strval) == 0) continue; } } if (need_comma) *cp++ = ','; else need_comma = true; col_ident = (char *) slon_quote_identifier(SPI_fname(tupdesc, i + 1)); if (new_isnull) col_value = "NULL"; else { OldDateStyle = GetConfigOptionByName("DateStyle", NULL); if (!strstr(OldDateStyle, "ISO")) set_config_option("DateStyle", "ISO", PGC_USERSET, PGC_S_SESSION, true, true); col_value = slon_quote_literal(SPI_getvalue(new_row, tupdesc, i + 1)); if (!strstr(OldDateStyle, "ISO")) set_config_option("DateStyle", OldDateStyle, PGC_USERSET, PGC_S_SESSION, true, true); } cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 + (len_ident = strlen(col_ident)) + (len_value = strlen(col_value)); if (cs->cmddata_size < cmddata_need) { int have = (cp - (char *) (cs->cmddata_buf)); while (cs->cmddata_size < cmddata_need) cs->cmddata_size *= 2; cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size); cp = (char *) (cs->cmddata_buf) + have; } memcpy(cp, col_ident, len_ident); cp += len_ident; *cp++ = '='; memcpy(cp, col_value, len_value); cp += len_value; } /* * It can happen that the only UPDATE an application does is to set a * column to the same value again. In that case, we'd end up here with * no columns in the SET clause yet. We add the first key column here * with it's old value to simulate the same for the replication * engine. */ if (!need_comma) { for (i = 0, attkind_idx = -1; i < tg->tg_relation->rd_att->natts; i++) { if (tupdesc->attrs[i]->attisdropped) continue; attkind_idx++; if (!attkind[attkind_idx]) elog(ERROR, "Slony-I: no key columns found in logTrigger() attkind parameter"); if (attkind[attkind_idx] == 'k') break; } col_ident = (char *) slon_quote_identifier(SPI_fname(tupdesc, i + 1)); col_value = slon_quote_literal(SPI_getvalue(old_row, tupdesc, i + 1)); cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 + (len_ident = strlen(col_ident)) + (len_value = strlen(col_value)); if (cs->cmddata_size < cmddata_need) { int have = (cp - (char *) (cs->cmddata_buf)); while (cs->cmddata_size < cmddata_need) cs->cmddata_size *= 2; cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size); cp = (char *) (cs->cmddata_buf) + have; } memcpy(cp, col_ident, len_ident); cp += len_ident; *cp++ = '='; memcpy(cp, col_value, len_value); cp += len_value; } *cp++ = ' '; *cp++ = 'w'; *cp++ = 'h'; *cp++ = 'e'; *cp++ = 'r'; *cp++ = 'e'; *cp++ = ' '; for (i = 0, attkind_idx = -1; i < tg->tg_relation->rd_att->natts; i++) { /* * Ignore dropped columns */ if (tupdesc->attrs[i]->attisdropped) continue; attkind_idx++; if (!attkind[attkind_idx]) break; if (attkind[attkind_idx] != 'k') continue; col_ident = (char *) slon_quote_identifier(SPI_fname(tupdesc, i + 1)); col_value = slon_quote_literal(SPI_getvalue(old_row, tupdesc, i + 1)); if (col_value == NULL) elog(ERROR, "Slony-I: old key column %s.%s IS NULL on UPDATE", NameStr(tg->tg_relation->rd_rel->relname), col_ident); cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 + (len_ident = strlen(col_ident)) + (len_value = strlen(col_value)); if (cs->cmddata_size < cmddata_need) { int have = (cp - (char *) (cs->cmddata_buf)); while (cs->cmddata_size < cmddata_need) cs->cmddata_size *= 2; cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size); cp = (char *) (cs->cmddata_buf) + have; } if (need_and) { *cp++ = ' '; *cp++ = 'a'; *cp++ = 'n'; *cp++ = 'd'; *cp++ = ' '; } else need_and = true; memcpy(cp, col_ident, len_ident); cp += len_ident; *cp++ = '='; memcpy(cp, col_value, len_value); cp += len_value; } *cp = '\0'; SET_VARSIZE(cs->cmddata_buf, VARHDRSZ + (cp - VARDATA(cs->cmddata_buf))); } else if (TRIGGER_FIRED_BY_DELETE(tg->tg_event)) { HeapTuple old_row = tg->tg_trigtuple; TupleDesc tupdesc = tg->tg_relation->rd_att; char *col_ident; char *col_value; int len_ident; int len_value; int i; int need_and = false; char *cp = VARDATA(cs->cmddata_buf); /* * DELETE * * cmdtype = 'D' cmddata = "pk_ident"='value' [and ...] */ cmdtype = cs->cmdtype_D; for (i = 0, attkind_idx = -1; i < tg->tg_relation->rd_att->natts; i++) { if (tupdesc->attrs[i]->attisdropped) continue; attkind_idx++; if (!attkind[attkind_idx]) break; if (attkind[attkind_idx] != 'k') continue; col_ident = (char *) slon_quote_identifier(SPI_fname(tupdesc, i + 1)); col_value = slon_quote_literal(SPI_getvalue(old_row, tupdesc, i + 1)); if (col_value == NULL) elog(ERROR, "Slony-I: old key column %s.%s IS NULL on DELETE", NameStr(tg->tg_relation->rd_rel->relname), col_ident); cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 + (len_ident = strlen(col_ident)) + (len_value = strlen(col_value)); if (cs->cmddata_size < cmddata_need) { int have = (cp - (char *) (cs->cmddata_buf)); while (cs->cmddata_size < cmddata_need) cs->cmddata_size *= 2; cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size); cp = (char *) (cs->cmddata_buf) + have; } if (need_and) { *cp++ = ' '; *cp++ = 'a'; *cp++ = 'n'; *cp++ = 'd'; *cp++ = ' '; } else need_and = true; memcpy(cp, col_ident, len_ident); cp += len_ident; *cp++ = '='; memcpy(cp, col_value, len_value); cp += len_value; } *cp = '\0'; SET_VARSIZE(cs->cmddata_buf, VARHDRSZ + (cp - VARDATA(cs->cmddata_buf))); } else elog(ERROR, "Slony-I: logTrigger() fired for unhandled event"); /* * Construct the parameter array and insert the log row. */ argv[0] = Int32GetDatum(tab_id); argv[1] = PointerGetDatum(cmdtype); argv[2] = PointerGetDatum(cs->cmddata_buf); SPI_execp(cs->plan_active_log, argv, NULL, 0); SPI_finish(); return PointerGetDatum(NULL); }
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 later use. */ if (SPI_keepplan(pplan)) /* internal error */ elog(ERROR, "check_foreign_key: SPI_keepplan failed"); 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: " UINT64_FORMAT " 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); }
static Datum tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column) { TriggerData *trigdata; Trigger *trigger; Relation rel; HeapTuple rettuple = NULL; int tsvector_attr_num, i; ParsedText prs; Datum datum; bool isnull; text *txt; Oid cfgId; /* Check call context */ if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */ elog(ERROR, "tsvector_update_trigger: not fired by trigger manager"); trigdata = (TriggerData *) fcinfo->context; if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) elog(ERROR, "tsvector_update_trigger: must be fired for row"); if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) elog(ERROR, "tsvector_update_trigger: 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 elog(ERROR, "tsvector_update_trigger: must be fired for INSERT or UPDATE"); trigger = trigdata->tg_trigger; rel = trigdata->tg_relation; if (trigger->tgnargs < 3) elog(ERROR, "tsvector_update_trigger: arguments must be tsvector_field, ts_config, text_field1, ...)"); /* Find the target tsvector column */ tsvector_attr_num = SPI_fnumber(rel->rd_att, trigger->tgargs[0]); if (tsvector_attr_num == SPI_ERROR_NOATTRIBUTE) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("tsvector column \"%s\" does not exist", trigger->tgargs[0]))); if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, tsvector_attr_num), TSVECTOROID)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("column \"%s\" is not of tsvector type", trigger->tgargs[0]))); /* Find the configuration to use */ if (config_column) { int config_attr_num; config_attr_num = SPI_fnumber(rel->rd_att, trigger->tgargs[1]); if (config_attr_num == SPI_ERROR_NOATTRIBUTE) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("configuration column \"%s\" does not exist", trigger->tgargs[1]))); if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, config_attr_num), REGCONFIGOID)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("column \"%s\" is not of regconfig type", trigger->tgargs[1]))); datum = SPI_getbinval(rettuple, rel->rd_att, config_attr_num, &isnull); if (isnull) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("configuration column \"%s\" must not be null", trigger->tgargs[1]))); cfgId = DatumGetObjectId(datum); } else { List *names; names = stringToQualifiedNameList(trigger->tgargs[1]); /* require a schema so that results are not search path dependent */ if (list_length(names) < 2) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("text search configuration name \"%s\" must be schema-qualified", trigger->tgargs[1]))); cfgId = get_ts_config_oid(names, false); } /* initialize parse state */ prs.lenwords = 32; prs.curwords = 0; prs.pos = 0; prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * prs.lenwords); /* find all words in indexable column(s) */ for (i = 2; i < trigger->tgnargs; i++) { int numattr; numattr = SPI_fnumber(rel->rd_att, trigger->tgargs[i]); if (numattr == SPI_ERROR_NOATTRIBUTE) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" does not exist", trigger->tgargs[i]))); if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, numattr), TEXTOID)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("column \"%s\" is not of a character type", trigger->tgargs[i]))); datum = SPI_getbinval(rettuple, rel->rd_att, numattr, &isnull); if (isnull) continue; txt = DatumGetTextP(datum); parsetext(cfgId, &prs, VARDATA(txt), VARSIZE(txt) - VARHDRSZ); if (txt != (text *) DatumGetPointer(datum)) pfree(txt); } /* make tsvector value */ if (prs.curwords) { datum = PointerGetDatum(make_tsvector(&prs)); rettuple = SPI_modifytuple(rel, rettuple, 1, &tsvector_attr_num, &datum, NULL); pfree(DatumGetPointer(datum)); } else { TSVector out = palloc(CALCDATASIZE(0, 0)); SET_VARSIZE(out, CALCDATASIZE(0, 0)); out->size = 0; datum = PointerGetDatum(out); rettuple = SPI_modifytuple(rel, rettuple, 1, &tsvector_attr_num, &datum, NULL); pfree(prs.words); } if (rettuple == NULL) /* internal error */ elog(ERROR, "tsvector_update_trigger: %d returned by SPI_modifytuple", SPI_result); return PointerGetDatum(rettuple); }
/* * Verify lvalue It doesn't repeat a checks that are done. Checks a subscript * expressions, verify a validity of record's fields. */ void plpgsql_check_target(PLpgSQL_checkstate *cstate, int varno, Oid *expected_typoid, int *expected_typmod) { PLpgSQL_datum *target = cstate->estate->datums[varno]; plpgsql_check_record_variable_usage(cstate, varno, true); switch (target->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) target; PLpgSQL_type *tp = var->datatype; if (expected_typoid != NULL) *expected_typoid = tp->typoid; if (expected_typmod != NULL) *expected_typmod = tp->atttypmod; } break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) target; #if PG_VERSION_NUM >= 110000 if (rec->rectypeid != RECORDOID) { if (expected_typoid != NULL) *expected_typoid = rec->rectypeid; if (expected_typmod != NULL) *expected_typmod = -1; } else #endif if (recvar_tupdesc(rec) != NULL) { if (expected_typoid != NULL) *expected_typoid = recvar_tupdesc(rec)->tdtypeid; if (expected_typmod != NULL) *expected_typmod = recvar_tupdesc(rec)->tdtypmod; } else { if (expected_typoid != NULL) *expected_typoid = RECORDOID; if (expected_typmod != NULL) *expected_typmod = -1; } } break; case PLPGSQL_DTYPE_ROW: { PLpgSQL_row *row = (PLpgSQL_row *) target; if (row->rowtupdesc != NULL) { if (expected_typoid != NULL) *expected_typoid = row->rowtupdesc->tdtypeid; if (expected_typmod != NULL) *expected_typmod = row->rowtupdesc->tdtypmod; } else { if (expected_typoid != NULL) *expected_typoid = RECORDOID; if (expected_typmod != NULL) *expected_typmod = -1; } plpgsql_check_row_or_rec(cstate, row, NULL); } break; case PLPGSQL_DTYPE_RECFIELD: { PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target; PLpgSQL_rec *rec; int fno; rec = (PLpgSQL_rec *) (cstate->estate->datums[recfield->recparentno]); /* * Check that there is already a tuple in the record. We need * that because records don't have any predefined field * structure. */ if (!HeapTupleIsValid(recvar_tuple(rec))) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("record \"%s\" is not assigned to tuple structure", rec->refname))); /* * Get the number of the records field to change and the * number of attributes in the tuple. Note: disallow system * column names because the code below won't cope. */ fno = SPI_fnumber(recvar_tupdesc(rec), recfield->fieldname); if (fno <= 0) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("record \"%s\" has no field \"%s\"", rec->refname, recfield->fieldname))); if (expected_typoid) *expected_typoid = SPI_gettypeid(recvar_tupdesc(rec), fno); if (expected_typmod) *expected_typmod = TupleDescAttr(recvar_tupdesc(rec), fno - 1)->atttypmod; } break; case PLPGSQL_DTYPE_ARRAYELEM: { /* * Target is an element of an array */ int nsubscripts; Oid arrayelemtypeid; Oid arraytypeid; /* * To handle constructs like x[1][2] := something, we have to * be prepared to deal with a chain of arrayelem datums. Chase * back to find the base array datum, and save the subscript * expressions as we go. (We are scanning right to left here, * but want to evaluate the subscripts left-to-right to * minimize surprises.) */ nsubscripts = 0; do { PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target; if (nsubscripts++ >= MAXDIM) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", nsubscripts + 1, MAXDIM))); plpgsql_check_expr(cstate, arrayelem->subscript); target = cstate->estate->datums[arrayelem->arrayparentno]; } while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM); /* * If target is domain over array, reduce to base type */ #if PG_VERSION_NUM >= 90600 arraytypeid = plpgsql_exec_get_datum_type(cstate->estate, target); #else arraytypeid = exec_get_datum_type(cstate->estate, target); #endif arraytypeid = getBaseType(arraytypeid); arrayelemtypeid = get_element_type(arraytypeid); if (!OidIsValid(arrayelemtypeid)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("subscripted object is not an array"))); if (expected_typoid) *expected_typoid = arrayelemtypeid; if (expected_typmod) *expected_typmod = ((PLpgSQL_var *) target)->datatype->atttypmod; plpgsql_check_record_variable_usage(cstate, target->dno, true); } break; default: ; /* nope */ } }
/* * Similar function exec_get_datum_type is in 9nth line */ static Oid exec_get_datum_type(PLpgSQL_execstate *estate, PLpgSQL_datum *datum) { Oid typoid = InvalidOid; switch (datum->dtype) { case PLPGSQL_DTYPE_VAR: typoid = ((PLpgSQL_var *) datum)->datatype->typoid; break; case PLPGSQL_DTYPE_ROW: typoid = ((PLpgSQL_row *) datum)->rowtupdesc->tdtypeid; break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) datum; if (!HeapTupleIsValid(rec->tup)) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("record \"%s\" is not assigned yet", rec->refname), errdetail("The tuple structure of a not-yet-assigned record is indeterminate."))); Assert(rec->tupdesc != NULL); /* Make sure we have a valid type/typmod setting */ BlessTupleDesc(rec->tupdesc); typoid = rec->tupdesc->tdtypeid; } break; case PLPGSQL_DTYPE_RECFIELD: { PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum; PLpgSQL_rec *rec; int fno; rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]); if (!HeapTupleIsValid(rec->tup)) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("record \"%s\" is not assigned yet", rec->refname), errdetail("The tuple structure of a not-yet-assigned record is indeterminate."))); fno = SPI_fnumber(rec->tupdesc, recfield->fieldname); if (fno == SPI_ERROR_NOATTRIBUTE) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("record \"%s\" has no field \"%s\"", rec->refname, recfield->fieldname))); typoid = SPI_gettypeid(rec->tupdesc, fno); } break; case PLPGSQL_DTYPE_TRIGARG: typoid = TEXTOID; break; } return typoid; }
Datum exec_sql_using(PG_FUNCTION_ARGS) { HeapTuple procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fcinfo->flinfo->fn_oid)); if (!HeapTupleIsValid(procedureTuple)) ereport(ERROR, (errmsg("cache lookup failed for function %u", fcinfo->flinfo->fn_oid))); Oid* types = NULL; char** names = NULL; char* modes = NULL; int nargs = get_func_arg_info(procedureTuple, &types, &names, &modes); Oid resultTypeOid; TupleDesc tupleDesc; TypeFuncClass resultType = get_call_result_type(fcinfo, &resultTypeOid, &tupleDesc); bool returnTypeIsByValue; int16 returnTypeLen; get_typlenbyval(resultTypeOid, &returnTypeLen, &returnTypeIsByValue); if (resultType != TYPEFUNC_SCALAR && resultType != TYPEFUNC_COMPOSITE) ereport(ERROR, ( errmsg("function \"%s\" has indeterminable result type", format_procedure(fcinfo->flinfo->fn_oid)) )); bool returnVoid = resultTypeOid == VOIDOID; ReleaseSysCache(procedureTuple); if (nargs < 2) ereport(ERROR, ( errmsg("function \"%s\" has less than 2 arguments", format_procedure(fcinfo->flinfo->fn_oid)) )); else if (modes != NULL) for (int i = 0; i < nargs; i++) { if (modes[i] != PROARGMODE_IN) ereport(ERROR, ( errmsg("function \"%s\" has non-IN arguments", format_procedure(fcinfo->flinfo->fn_oid)) )); } else if (PG_ARGISNULL(0)) ereport(ERROR, ( errmsg("function \"%s\" called with NULL as first argument", format_procedure(fcinfo->flinfo->fn_oid)) )); char* stmt = NULL; if (types[0] == TEXTOID) stmt = DatumGetCString( DirectFunctionCall1(textout, PG_GETARG_DATUM(0))); else if (types[0] == VARCHAROID) stmt = DatumGetCString( DirectFunctionCall1(varcharout, PG_GETARG_DATUM(0))); else ereport(ERROR, ( errmsg("function \"%s\" does not have a leading VARCHAR/TEXT " "argument", format_procedure(fcinfo->flinfo->fn_oid)) )); char* nulls = NULL; for (int i = 1; i < nargs; i++) if (PG_ARGISNULL(i)) { if (nulls == NULL) { nulls = palloc0(sizeof(char) * (nargs - 1)); memset(nulls, ' ', nargs - 1); } nulls[i - 1] = 'n'; } SPI_connect(); SPIPlanPtr plan = SPI_prepare(stmt, nargs - 1, &types[1]); if (plan == NULL) ereport(ERROR, ( errmsg("function \"%s\" could not obtain execution plan for " "SQL statement", format_procedure(fcinfo->flinfo->fn_oid)) )); int result = SPI_execute_plan(plan, &fcinfo->arg[1], nulls, false, returnVoid ? 0 : 1); Datum returnValue = 0; bool returnNull = false; if (!returnVoid) { if (result != SPI_OK_SELECT && result != SPI_OK_INSERT_RETURNING && result != SPI_OK_DELETE_RETURNING && result == SPI_OK_UPDATE_RETURNING) ereport(ERROR, ( errmsg("function \"%s\" could not obtain result from query", format_procedure(fcinfo->flinfo->fn_oid)) )); else if (SPI_tuptable->tupdesc->natts != 1) ereport(ERROR, ( errmsg("function \"%s\" retrieved more than one column from " "query", format_procedure(fcinfo->flinfo->fn_oid)) )); else if (resultTypeOid != SPI_gettypeid(SPI_tuptable->tupdesc, 1)) ereport(ERROR, ( errmsg("function \"%s\" has different return type OID than " "what query returned", format_procedure(fcinfo->flinfo->fn_oid)) )); /* It is important to copy the value into the upper executor context, * i.e., the memory context that was current when SPI_connect was * called */ returnValue = SPI_getbinval(SPI_copytuple(SPI_tuptable->vals[0]), SPI_tuptable->tupdesc, 1, &returnNull); } SPI_freeplan(plan); if (nulls) pfree(nulls); SPI_finish(); if (result < 0) ereport(ERROR, ( errmsg("function \"%s\" encountered error %d during SQL execution", format_procedure(fcinfo->flinfo->fn_oid), result) )); if (returnVoid) PG_RETURN_VOID(); else if (returnNull) PG_RETURN_NULL(); else return returnValue; }
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); }
static TSVectorStat * ts_stat_sql(MemoryContext persistentContext, text *txt, text *ws) { char *query = text_to_cstring(txt); int i; TSVectorStat *stat; bool isnull; Portal portal; SPIPlanPtr plan; if ((plan = SPI_prepare(query, 0, NULL)) == NULL) /* internal error */ elog(ERROR, "SPI_prepare(\"%s\") failed", query); if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL) /* internal error */ elog(ERROR, "SPI_cursor_open(\"%s\") failed", query); SPI_cursor_fetch(portal, true, 100); if (SPI_tuptable == NULL || SPI_tuptable->tupdesc->natts != 1 || !IsBinaryCoercible(SPI_gettypeid(SPI_tuptable->tupdesc, 1), TSVECTOROID)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("ts_stat query must return one tsvector column"))); stat = MemoryContextAllocZero(persistentContext, sizeof(TSVectorStat)); stat->maxdepth = 1; if (ws) { char *buf; buf = VARDATA(ws); while (buf - VARDATA(ws) < VARSIZE(ws) - VARHDRSZ) { if (pg_mblen(buf) == 1) { switch (*buf) { case 'A': case 'a': stat->weight |= 1 << 3; break; case 'B': case 'b': stat->weight |= 1 << 2; break; case 'C': case 'c': stat->weight |= 1 << 1; break; case 'D': case 'd': stat->weight |= 1; break; default: stat->weight |= 0; } } buf += pg_mblen(buf); } } while (SPI_processed > 0) { for (i = 0; i < SPI_processed; i++) { Datum data = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull); if (!isnull) stat = ts_accum(persistentContext, stat, data); } SPI_freetuptable(SPI_tuptable); SPI_cursor_fetch(portal, true, 100); } SPI_freetuptable(SPI_tuptable); SPI_cursor_close(portal); SPI_freeplan(plan); pfree(query); return stat; }
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_STATEMENT(trigdata->tg_event)) /* internal error */ elog(ERROR, "cannot process STATEMENT events"); if (TRIGGER_FIRED_AFTER(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); }
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_STATEMENT(trigdata->tg_event)) elog(ERROR, "timetravel: can't process STATEMENT events"); /* Should be called BEFORE */ if (TRIGGER_FIRED_AFTER(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 = DirectFunctionCall1(textin, CStringGetDatum(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 can't 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) { void *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 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); }