/** * @file agg_token.c * @brief PostgreSQL I/O functions and cast for the @c agg_token composite type. * * Implements the three SQL-callable C functions that back the * @c agg_token type: * - @c agg_token_in() – text → agg_token (input function) * - @c agg_token_out() – agg_token → text (output function) * - @c agg_token_cast() – agg_token → text (cast, extracts the UUID part) * * The on-wire text format is @c ( UUID , value ) where @c UUID is the * 36-character hyphenated UUID of the provenance gate and @c value is * the aggregate running value. */ #include "postgres.h" #include "fmgr.h" #include "catalog/pg_type.h" #include "utils/uuid.h" #include "utils/numeric.h" #include "utils/fmgrprotos.h" #include "executor/spi.h" #include "access/htup_details.h" #include "provsql_utils.h" #include "agg_token.h" PG_FUNCTION_INFO_V1(agg_token_in); /** * @brief Parse an @c agg_token value from its text representation. * * Expected format: @c "( UUID , value )" with a single space around the * comma and at the outer parentheses. Raises @c ERROR on malformed input. * @return Pointer to the newly allocated @c agg_token. */ Datum agg_token_in(PG_FUNCTION_ARGS) { char *str = PG_GETARG_CSTRING(0); agg_token* result; const unsigned toklen=sizeof(result->tok)-1; unsigned vallen; result = (agg_token *)palloc(sizeof(agg_token)); // str is ( UUID , string ) with UUID starting at 2 and with length // 20 (2*UUID-LEN=16) plus 4 hashes; then three characters we can // ignore (two spaces and comma) then the string then two ignored // spaces at the end if(strlen(str)tok, str+2, toklen); result->tok[toklen]='\0'; vallen=strlen(str)-toklen-2-3-2; if(vallen>=sizeof(result->val)) vallen=sizeof(result->val)-1; strncpy(result->val, str+2+toklen+3, vallen); result->val[vallen]='\0'; PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(agg_token_out); /** * @brief Produce a display string for an @c agg_token. * * Default: returns @c "value (*)" (the running value followed by * @c " (*)"), matching @c EXPLAIN and direct @c CAST to text. * * When the @c provsql.aggtoken_text_as_uuid GUC is on, returns the * underlying provenance UUID instead. This is the form ProvSQL * Studio enables per session so agg_token cells in a result table * expose the circuit root UUID for click-through; the user-facing * @c "value (*)" string is recovered via the * @c provsql.agg_token_value_text(uuid) helper. * * @return C-string representation of the agg_token. */ Datum agg_token_out(PG_FUNCTION_ARGS) { agg_token *aggtok = (agg_token *) PG_GETARG_POINTER(0); char *result; if (provsql_aggtoken_text_as_uuid) result = psprintf("%s", aggtok->tok); else result = psprintf("%s (*)", aggtok->val); PG_RETURN_CSTRING(result); } PG_FUNCTION_INFO_V1(agg_token_cast); /** * @brief Cast an @c agg_token to @c text, returning only the UUID part. * * This is used when the caller needs the provenance circuit UUID * stored in the token rather than the aggregate value. * @return Text datum containing the UUID string of the token. */ Datum agg_token_cast(PG_FUNCTION_ARGS) { agg_token *aggtok = (agg_token *) PG_GETARG_POINTER(0); char *result; text *txt_result; int len; result = psprintf("%s", aggtok->tok); len = strlen(result); txt_result = (text *) palloc(len + ((int32) sizeof(int32))); SET_VARSIZE(txt_result, len + ((int32) sizeof(int32))); memcpy(VARDATA(txt_result), result, len); PG_RETURN_TEXT_P(txt_result); } /** * @brief True when an @c agg_token value string cannot carry a number. * * An empty or literal @c "NULL" value string (an aggregate whose value is * SQL NULL) must convert to SQL NULL rather than feed the type-input * function a string it rejects. */ static bool agg_token_val_is_null(const agg_token *aggtok) { return aggtok->val[0] == '\0' || strcmp(aggtok->val, "NULL") == 0; } PG_FUNCTION_INFO_V1(agg_token_to_numeric); /** * @brief Cast an @c agg_token to @c numeric, extracting only the value. * * Emits a WARNING that provenance information is lost during the conversion. * @return Numeric datum parsed from the aggregate value string. */ Datum agg_token_to_numeric(PG_FUNCTION_ARGS) { agg_token *aggtok = (agg_token *) PG_GETARG_POINTER(0); Datum result; provsql_warning("converting agg_token to numeric: provenance information is lost"); if (agg_token_val_is_null(aggtok)) PG_RETURN_NULL(); result = DirectFunctionCall3(numeric_in, CStringGetDatum(aggtok->val), ObjectIdGetDatum(InvalidOid), Int32GetDatum(-1)); PG_RETURN_DATUM(result); } PG_FUNCTION_INFO_V1(agg_token_value); /** * @brief Extract the running value of an @c agg_token as @c numeric. * * Unlike @c agg_token_to_numeric, this does @b not emit the * "provenance information is lost" warning: it is the internal accessor * used by the provenance-preserving @c agg_token arithmetic operators * (which keep the provenance in a @c gate_arith circuit), where no * provenance is dropped. * @return Numeric datum parsed from the aggregate value string. */ Datum agg_token_value(PG_FUNCTION_ARGS) { agg_token *aggtok = (agg_token *) PG_GETARG_POINTER(0); Datum result; if (agg_token_val_is_null(aggtok)) PG_RETURN_NULL(); result = DirectFunctionCall3(numeric_in, CStringGetDatum(aggtok->val), ObjectIdGetDatum(InvalidOid), Int32GetDatum(-1)); PG_RETURN_DATUM(result); } PG_FUNCTION_INFO_V1(agg_token_to_float8); /** * @brief Cast an @c agg_token to @c double precision, extracting only the value. * * Emits a WARNING that provenance information is lost during the conversion. * @return Float8 datum parsed from the aggregate value string. */ Datum agg_token_to_float8(PG_FUNCTION_ARGS) { agg_token *aggtok = (agg_token *) PG_GETARG_POINTER(0); Datum result; provsql_warning("converting agg_token to double precision: provenance information is lost"); if (agg_token_val_is_null(aggtok)) PG_RETURN_NULL(); result = DirectFunctionCall1(float8in, CStringGetDatum(aggtok->val)); PG_RETURN_DATUM(result); } PG_FUNCTION_INFO_V1(agg_token_to_int4); /** * @brief Cast an @c agg_token to @c integer, extracting only the value. * * Emits a WARNING that provenance information is lost during the conversion. * @return Int32 datum parsed from the aggregate value string. */ Datum agg_token_to_int4(PG_FUNCTION_ARGS) { agg_token *aggtok = (agg_token *) PG_GETARG_POINTER(0); Datum result; provsql_warning("converting agg_token to integer: provenance information is lost"); if (agg_token_val_is_null(aggtok)) PG_RETURN_NULL(); result = DirectFunctionCall1(int4in, CStringGetDatum(aggtok->val)); PG_RETURN_DATUM(result); } PG_FUNCTION_INFO_V1(agg_token_to_int8); /** * @brief Cast an @c agg_token to @c bigint, extracting only the value. * * Emits a WARNING that provenance information is lost during the conversion. * @return Int64 datum parsed from the aggregate value string. */ Datum agg_token_to_int8(PG_FUNCTION_ARGS) { agg_token *aggtok = (agg_token *) PG_GETARG_POINTER(0); Datum result; provsql_warning("converting agg_token to bigint: provenance information is lost"); if (agg_token_val_is_null(aggtok)) PG_RETURN_NULL(); result = DirectFunctionCall1(int8in, CStringGetDatum(aggtok->val)); PG_RETURN_DATUM(result); } PG_FUNCTION_INFO_V1(agg_token_to_text); /** * @brief Cast an @c agg_token to @c text, extracting only the value. * * Unlike @c agg_token_cast which returns the UUID part, this returns * the aggregate value as text. * Emits a WARNING that provenance information is lost during the conversion. * @return Text datum containing the aggregate value string. */ Datum agg_token_to_text(PG_FUNCTION_ARGS) { agg_token *aggtok = (agg_token *) PG_GETARG_POINTER(0); text *txt_result; int len; provsql_warning("converting agg_token to text: provenance information is lost"); len = strlen(aggtok->val); txt_result = (text *) palloc(len + VARHDRSZ); SET_VARSIZE(txt_result, len + VARHDRSZ); memcpy(VARDATA(txt_result), aggtok->val, len); PG_RETURN_TEXT_P(txt_result); }