libpqxx C Aggregate Extension returns wrong data?

148 views Asked by At

I am learning how to create C aggregate extensions and using libpqxx with C++ on the client side to process the data.

My toy aggregate extension has one argument of type bytea, and the state is also of type bytea. The following is the simplest example of my problem:

Server side:

PG_FUNCTION_INFO_V1( simple_func );

Datum simple_func( PG_FUNCTION_ARGS ){

    bytea *new_state   = (bytea *) palloc( 128 + VARHDRSZ );
    memset(new_state, 0, 128 + VARHDRSZ );
    SET_VARSIZE( new_state,128 + VARHDRSZ );

    PG_RETURN_BYTEA_P( new_state );

}

Client side:

std::basic_string< std::byte > buffer;

pqxx::connection c{"postgresql://user:simplepassword@localhost/contrib_regression"};
pqxx::work w(c);
c.prepare( "simple_func", "SELECT simple_func( $1 )  FROM table" );
pqxx::result r = w.exec_prepared( "simple_func", buffer );

for (auto row: r){ 
        cout << "  Result Size: " << row[ "simple_func" ].size() << endl;
        cout << "Raw Result Data: ";
        for( int jj=0; jj < row[ "simple_func" ].size(); jj++ ) printf( "%02" PRIx8,   (uint8_t) row[ "simple_func" ].c_str()[jj] )  ;
        cout << endl;
}

The result on the client side prints :

Result Size: 258
Raw Result Data: 5c783030303030303030303030303030...

Where the 30 pattern repeats until the end of the string and the printed string in hex is 512 bytes.

I expected to receive an array of length 128 bytes where every byte is set to zero. What am I doing wrong?

The libpqxx version is 7.2 and PostgreSQL 12 on Ubuntu 20.04.

Addendum

Installation of the extesion sql statement;

CREATE OR REPLACE FUNCTION agg_simple_func( state bytea, arg1 bytea)
RETURNS bytea
AS '$libdir/agg_simple_func'
LANGUAGE C IMMUTABLE STRICT;

CREATE OR REPLACE AGGREGATE simple_func( arg1 bytea)  
(
    sfunc = agg_simple_func,
    stype = bytea,
    initcond = "\xFFFF" 
);
2

There are 2 answers

2
ReverseFlowControl On BEST ANSWER

The answer appears to be that the bytea type data on the client side must be retrieved as follows in the libpqxx library as of 7.0 (Not tested in earlier versions):

row[ "simple_func" ].as<std::basic_string<std::byte>>()

This retrieves the right bytea data without any conversions, string idiosyncrasies or unexpected behavior like I was seeing.

7
Laurenz Albe On

I recommend that you tackle these things one by one: first get the function to work, testing it with psql in interactive queries, then write the client code (or vice versa).

I can't speak about libpqxx, but I have to complain about your function: what you presented won't even compile, because you wrote DATUM in upper case and forgot headers and other important stuff.

This function will compile and run as you expect:

#include "postgres.h"
#include "fmgr.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(simplest_func);

Datum simplest_func(PG_FUNCTION_ARGS) {
    bytea *new_state = (bytea *) palloc(128 + VARHDRSZ);
    memset(new_state, 0, 128 + VARHDRSZ);
    SET_VARSIZE(new_state, 128 + VARHDRSZ);

    PG_RETURN_BYTEA_P(new_state);
}

The memset will work that way, but the better and more idiomatic and robust way to set the value of a varlena is

    memset(VARDATA(new_state), 0, 128);

I have no idea, how you got your result, but since the code you presented doesn't compile, I don't know how your function really looks.