udf_rg_3

The sample table UDF udf_rg_3 builds upon udf_rg_2 and has similar behavior. The procedure is called udf_rg_3 and its implementation is in the samples directory in udf_rg_3.cxx.

The difference between the behavior of table UDFs udf_rg_3 and udf_rg_2 is that udf_rg_3 generates only 100 unique values from 0 to 99, then repeats the sequence as necessary. This table UDF provides _start_extfn and _finish_extfn methods and has a modified version of _describe_extfn to account for the different semantics of the function.

Using fetch_block instead of fetch_into allows the table UDF to own the row block structure and use its own data layout. To illustrate this, the numbers generated are pre-allocated in an array. When a fetch is performed, rather than copying data into the server provided row block, the table UDF points the row block data pointers directly to the memory containing the data, thus preventing additional copies.

The following ancillary structure stores the numbers array. This structure also keeps a pointer to the allocated row block, which deallocates the row block.

#define MAX_ROWS 100
struct RowData {

    a_sql_int32            numbers[MAX_ROWS];
    a_sql_uint32           piece_len;
    a_v4_extfn_row_block * rows;

    void Init()
    {
	       rows = NULL;
	       piece_len = sizeof( a_sql_int32 );
	       for( int i = 0; i < MAX_ROWS; i++ ) {
	           numbers[i] = i;
	       }
    }
};

This structure is allocated when execution of the table UDF starts, and deallocated when execution finishes, by providing _start_extfn and _finish_extfn methods in the a_v4_extfn_proc_context.

static void UDF_CALLBACK udf_rg_start( 
	      a_v4_extfn_proc_context *ctx )
/*************************************/
{
    // The start_extfn method is a good place to allocate our row
    // data.  This method is called only once at the beginning of
    // execution.
    RowData *row_data = (RowData *)
	      ctx->alloc( ctx, sizeof( RowData ) );
    row_data->Init();
    ctx->_user_data = row_data;
}

The finish method performs two functions:

static void UDF_CALLBACK udf_rg_finish( 
	      a_v4_extfn_proc_context *ctx )
/**************************************/
{
    if( ctx->_user_data != NULL ) {

	       RowData *row_data = (RowData *)ctx->_user_data;

	
	       // If rows is non-null here, it means an error occurred and
	       // fetch_block did not complete.
       	if( row_data->rows != NULL ) {
	           DestroyRowBlock( ctx, row_data->rows, 0, false );
	       }

	       ctx->free( ctx, ctx->_user_data );
	       ctx->_user_data = NULL;
    }
}

The fetch_block method is:

static short UDF_CALLBACK udf_rg_fetch_block(
    	  a_v4_extfn_table_context *tctx,
    	  a_v4_extfn_row_block **rows )
/*********************************************/
{
    udf_rg_state *	state 		= (udf_rg_state*)tctx->user_data;
    RowData *		row_data	= (RowData *)tctx->proc_context->_user_data;
    
    // First call, we need to build the row block
    if( *rows == NULL ) {

	      // This function will build a row block structure that holds
	      // MAX_ROWS rows of data.  See udf_utils.cxx for details.
	      *rows = BuildRowBlock( tctx->proc_context, 0, MAX_ROWS, false );

	      // This pointer gets saved here because in some circumstances
	      // when an error occurs, its possible we may have allocated
	      // the rowblock structure but then never called back into
	      // fetch_block to deallocate it.  In this case, when the finish
	      // method is called, we will end up deallocating it there.
	      row_data->rows = *rows;
    }

    (*rows)->num_rows = 0;

    // The row block we allocated contains a max_rows member that was
    // set to the macro MAX_ROWS (100 in this case). This field is the
    // maximum number of rows that this row block can handle.  We can
    // not exceed this number.  We will also stop producing rows when
    // we have produced the number of rows required as per the max_row
    // in the state.
    while( (*rows)->num_rows < (*rows)->max_rows && 
	         state->next_row < state->max_row ) {

	      a_v4_extfn_row  	&row = (*rows)->row_data[ (*rows)->num_rows ];
	      a_v4_extfn_column_data	&col0 = row.column_data[ 0 ];

	      // Row generation here is a matter of pointing the data
	      // pointer in the rowblock to our pre-allocated array of
	      // integers that was stored in the proc_context.
	      col0.data = &row_data->numbers[(*rows)->num_rows % MAX_ROWS];
	      col0.max_piece_len = sizeof( a_sql_int32 );
	      col0.piece_len = &row_data->piece_len;
	      state->next_row++;
	      (*rows)->num_rows++;
    }
    if( (*rows)->num_rows > 0 ) {
	      return 1;
    } else {
	     // When we are finished generating data, we can destroy the
	     // row block structure.
	     DestroyRowBlock( tctx->proc_context, *rows, 0, false );
	     row_data->rows = NULL;
	     return 0;
    }
}

The first time this method is called, a row block is allocated using the helper function BuildRowBlock, which is in udf_utils.cxx. A pointer to this row block is saved in the RowData structure for later use.

Row generation is achieved by setting the data pointer for the column data to the address of the next number in sequence in the previously allocated numbers array. The piece_len pointer for the column data must also be initialized, by setting it to the address of the piece_len member of RowData. Since the rows are a fixed data length, this number is the same for all rows.

When fetch is called the last time and there is no more data to produce, the row block structure is destroyed using the DestroyRowBlock helper function in udf_utils.cxx.

To accommodate this table UDF generating only 100 unique values, EXTFNAPIV4_DESCRIBE_COL_DISTINCT_VALUES is set to a value of 100. This code excerpt from the describe method demonstrates this:

static void UDF_CALLBACK udf_rg_describe(
    	  a_v4_extfn_proc_context *ctx )
/*****************************************************************/
{
...
...
...

    a_v4_extfn_estimate distinct = { 
	      MAX_ROWS, 1.0
    };

    // Inform the server that this UDF will produce MAX_ROWS
    // distinct values for column 1 of its result set.
    desc_rc = ctx->describe_column_set
	      ( ctx,
	        0,
	        1,
	        EXTFNAPIV4_DESCRIBE_COL_DISTINCT_VALUES,
	        &distinct,
	        sizeof( distinct ) );

    UDF_CHECK_DESCRIBE( ctx, desc_rc );
...
...
...

}
Related concepts
udf_rg_2
Related tasks
Implementing Sample Table UDF udf_rg_1