/*--------------------------------------------------------------------------- * MyUdt datablade * * This is a bare-bones datablade which illustrates how to implement * a multirep, variable length user defined type. * * The only functions which are supported are text input and output, * large object support (assign, destroy, lohandles), and a few * simple access methods. * *--------------------------------------------------------------------------*/ #include #include #include /* Large object threshold, in bytes. * This is the crossover point above which our variable-length udt gets * converted to a large object before storage in a table. It is set at * 960 so that two 'small' objects will fit on an rtree leaf page. * See the Readme file for this example for a more complete explanation * of this. */ #define LO_THRESHOLD 960 typedef mi_double_precision mi_double; /* For convenience */ /* Enumerated type describing the state of the multirep data */ typedef enum { MR_Undefined = 0, MR_Small, /* data is small enough to store in-row */ MR_Buffered, /* data is stored in a per-command buffer */ MR_Large /* data must be pinned */ } MR_StateType; /* * Internal data structure of the variable length udt. * Note that there is a fixed-length section followed by a variable * length section. The fixed-length section should contain, among * other things, information on the format and length of the * variable-length section. * This structure maintains 8-byte alignment since the multirep * data area may contain double precision floating point values. */ typedef struct { mi_integer nelems; /* no. of elements in array */ MR_StateType mrep_state; /* tells what's in mrep field */ MI_MULTIREP_DATA mrep; /* the variable length section */ } MyUdtData; /* Function prototypes */ void* PinMultirepData ( MyUdtData* ); void UnpinMultirepData ( MyUdtData* ); MI_LO_HANDLE* BuildLargeObject ( MI_CONNECTION*, const mi_char*, mi_integer, MI_FPARAM* ); mi_bitvarying* MyUdtCtor ( mi_integer, void** ); /*--------------------------------------------------------------------------- * MyUdtIn - text input routine for MyUdt variable length datatype * * This is a bare-bones input parser. It does absolutely no error * checking, does not use GLS routines, and should under no circumstances * be used in production code. It is for illustrative purposes only. * * Text input data format: * (val, val, ...) * Example: * (10, 20, 30, 40) *--------------------------------------------------------------------------*/ mi_bitvarying* MyUdtIn ( mi_lvarchar* input_varchar, MI_FPARAM* fp ) { mi_bitvarying* retval; mi_char* token; mi_char* textbuf; mi_integer i; mi_integer nelems; mi_pointer dptr; mi_double* data_buf; /* Obtain pointer to text inside the lvarchar */ textbuf = (mi_char*) mi_get_vardata ( input_varchar ); /* * Make a pass through the input string, counting commas * to determine the number of elements in the array. */ nelems = 1; for ( i = 0; i < mi_get_varlen ( input_varchar ); i++ ) { if ( textbuf[i] == ',' ) nelems++; } retval = MyUdtCtor ( nelems, &dptr ); /* * Parse the input text string and copy point values into the data * buffer. WARNING! This is a bare bones parser. It is not thread * safe, it does not support internationalization, and it does no * error checking. It should not be used in production code. */ while ( *textbuf != '(' ) textbuf++; /* advance past leading whitespace */ textbuf++; /* advance past leading paren */ token = strtok ( textbuf, ",)" ); /* prime the pump */ data_buf = dptr; for ( i = 0; i < nelems; i++ ) { *data_buf++ = atof ( token ); token = strtok ( NULL, ",)" ); } return retval; } /*--------------------------------------------------------------------------- * MyUdtCtor * * This routine encapsulates most of the multirep-related work involved * in creating a new instance of MyUdt. It should be called by both * text input support functions and operators which need to make a * copy of an existing udt. *--------------------------------------------------------------------------*/ mi_bitvarying * MyUdtCtor ( mi_integer nelems, void** data_ptr ) { mi_bitvarying* retval; mi_integer data_len; mi_integer varchar_len; MyUdtData* objdata; /* * Compute size of output lvarchar. This is the size of the fixed * portion of the MyUdtData structure, plus the size of the variable * portion, which depends on the number of elements in the array. */ data_len = nelems * sizeof(mi_double); varchar_len = sizeof(MyUdtData) - sizeof(MI_MULTIREP_DATA) + data_len; if ( varchar_len > LO_THRESHOLD ) { /* * Data is too big to fit in-row, so allocate a per-command * memory buffer and store the address of the buffer in the * lvarchar. If the object is to be inserted into a table, * it will get promoted to a large object in Assign. */ *data_ptr = (void*) mi_dalloc ( data_len, PER_COMMAND ); /* Create an lvarchar big enough to hold the buffer pointer. * Note that we allocate a little more space than we really * need at this time so that there will be room in the lvarchar * at Assign time to hold a large object handle. */ retval = (mi_bitvarying*) mi_new_var ( sizeof(MyUdtData) ); objdata = (MyUdtData*) mi_get_vardata ( (mi_lvarchar*) retval ); objdata->mrep.mr_data = *data_ptr; objdata->mrep_state = MR_Buffered; } else { /* * Data is small enough to fit in-row, so create an lvarchar * big enough to hold all the data. */ retval = (mi_bitvarying*) mi_new_var ( varchar_len ); objdata = (MyUdtData*) mi_get_vardata ( (mi_lvarchar*) retval ); *data_ptr = (mi_double*) &objdata->mrep.mr_data; objdata->mrep_state = MR_Small; } objdata->nelems = nelems; return retval; } /*--------------------------------------------------------------------------- * MyUdtOut - text output routine for MyUdt variable length datatype * * This is a bare-bones output function. It does not use GLS routines * and should under no circumstances be used in production code. * It is for illustrative purposes only. *--------------------------------------------------------------------------*/ mi_lvarchar* MyUdtOut ( mi_bitvarying* obj, MI_FPARAM* fp ) { MyUdtData * objdata; mi_integer i; mi_string * textbuf; mi_lvarchar* retval; mi_char numbuf[20]; mi_double* point_data; objdata = (MyUdtData*) mi_get_vardata ( (mi_lvarchar*) obj ); point_data = (mi_double*) PinMultirepData ( objdata ); /* Allocate a text buffer big enough to hold all elements in array */ textbuf = (mi_string*) mi_alloc ( objdata->nelems * 20 ); strcpy ( textbuf, "(" ); for ( i = 0; i < objdata->nelems; i++ ) { if ( i != 0 ) strcat ( textbuf, ", " ); sprintf ( numbuf, "%0.3f", *point_data++ ); strcat ( textbuf, numbuf ); } strcat ( textbuf, ")" ); retval = mi_string_to_lvarchar ( textbuf ); UnpinMultirepData ( objdata ); mi_free ( textbuf ); return retval; } /*--------------------------------------------------------------------------- * MyUdtAssign (server-callable) * * This function gets called just before an instance of the udt is * inserted into a table. The udt that is passed in can have its * data in one of three states: * * Small: data is in the MyUdtData structure itself, immediately after * the fixed length portion of the structure. In this case * the udt can be stored in a table as-is. * Buffered: data is in another, per-command memory buffer. The address * of the buffer is in the MyUdtData structure. In this case * the data must be converted to a large object (since it is * too big to fit in-row). * Large: data is already a large object. This will be the case when * you are copying a udt from the same or another table. *--------------------------------------------------------------------------*/ mi_bitvarying* MyUdtAssign ( mi_bitvarying* obj, MI_FPARAM* fp ) { MyUdtData* objdata; objdata = (MyUdtData*) mi_get_vardata ( (mi_lvarchar*) obj ); switch ( objdata->mrep_state ) { case MR_Small: /* nothing to do: this is not a large object */ break; case MR_Large: { /* This is already a large object, so just bump its * refcount. This case will occur when a large object * is stored in more than one row or table. */ MI_CONNECTION* conn = mi_open ( NULL, NULL, NULL ); mi_lo_increfcount(conn, &objdata->mrep.mr_lo); mi_close(conn); break; } case MR_Buffered: { MI_LO_HANDLE* lohandle; MI_CONNECTION* conn = mi_open ( NULL, NULL, NULL ); mi_char* data = objdata->mrep.mr_data; mi_integer dlen = objdata->nelems * sizeof(mi_double); /* Promote the data to a large object */ lohandle = BuildLargeObject ( conn, data, dlen, fp ); /* Free the per-command memory buffer that the data were * stored in prior to promotion to a large object. */ mi_free ( data ); objdata->mrep_state = MR_Large; /* Fill in the mrep union structure with the lohandle. * Note that since we previously allocated enough memory * in the lvarchar to hold the lohandle, we don't have * to create a new lvarchar here. */ memcpy ( &objdata->mrep.mr_lo, (mi_char*) lohandle, sizeof(MI_LO_HANDLE) ); /* Zero out the pinned data buffer pointer, since we * use this in the pin/unpin routines to indicate * whether the large object has been pinned yet. */ objdata->mrep.mr_lo_pin_data = NULL; /* Free the lohandle that was allocated by BuildLargeObject */ mi_free ( (mi_char*) lohandle ); /* Bump the large object's ref count */ mi_lo_increfcount(conn, &objdata->mrep.mr_lo); mi_close(conn); break; } } return obj; } /*--------------------------------------------------------------------------- * MyUdtDestroy (server-callable) * * Called when a udt is removed from a table. *--------------------------------------------------------------------------*/ void MyUdtDestroy ( mi_bitvarying* obj, MI_FPARAM* fp ) { MyUdtData* objdata; objdata = (MyUdtData*) mi_get_vardata ( (mi_lvarchar*) obj ); switch ( objdata->mrep_state ) { case MR_Small: /* Nothing to do: this is not a large object */ break; case MR_Large: { /* Decrement the ref count. When it reaches zero, * the server will know that it is ok to remove * the large object from sbspace. */ MI_CONNECTION* conn = mi_open ( NULL, NULL, NULL ); mi_lo_decrefcount(conn, &objdata->mrep.mr_lo); mi_close(conn); break; } case MR_Buffered: /* Should never happen; production code should * call mi_db_error_raise in this case. */ break; } } /*-------------------------------------------------------------------------- * MyUdtLOhandles (server-callable) * * This routine returns the lo_handle by packaging it up in an lo_list * structure, and wrapping the whole thing in an lvarchar. For small * objects, there is some uncertainty as to what exactly to return. * See comments below for options. *-------------------------------------------------------------------------*/ /* This data structure is returned by LOhandles, wrapped in an lvarchar */ typedef struct { mi_integer nlos; /* Number of large object handles */ MI_LO_HANDLE los[1]; /* Valid large object handles */ } lo_list_struct; mi_bitvarying* MyUdtLOhandles ( mi_bitvarying* obj, MI_FPARAM* fp ) { mi_bitvarying* retval; lo_list_struct* lo_list; MyUdtData* objdata; objdata = (MyUdtData*) mi_get_vardata ( (mi_lvarchar*) obj ); /* Allocate an lvarchar big enough to hold an lo_list structure */ retval = (mi_bitvarying *) mi_new_var ( sizeof(lo_list_struct) ); lo_list = (lo_list_struct *) mi_get_vardata ( (mi_lvarchar *) retval ); switch ( objdata->mrep_state ) { case MR_Small: case MR_Buffered: /* At this time (5/97), there is some uncertainty as to what * to return, since the server does not yet use this routine. * Options include: * 1. A valid lo_list structure, with nlos = 0 * 2. null, with fparam returnisnull flag set * 3. null * For now, we'll do #1, since the lo_list code in the server * can deal with such a structure. */ #define LOH_Option1 #ifdef LOH_Option1 lo_list->nlos = 0; memset ( &lo_list->los[0], 0, sizeof(MI_LO_HANDLE) ); #endif #ifdef LOH_Option2 mi_fp_setreturnisnull ( fp, 0, MI_TRUE ); return (mi_bitvarying *) NULL; #endif #ifdef LOH_Option3 return (mi_bitvarying *) NULL; #endif break; case MR_Large: { /* Fill in the lo_list data structure */ lo_list->nlos = 1; memcpy ( &lo_list->los[0], &objdata->mrep.mr_lo, sizeof(MI_LO_HANDLE) ); break; } } return retval; } /*--------------------------------------------------------------------------- * MyUdtElement (server-callable) * * This is a simple access method which illustrates the use of the udt. * All the work is done by PinMultirepData. * * Returns the value of a specified element in the udt's data array. *--------------------------------------------------------------------------*/ mi_double* MyUdtElement ( mi_bitvarying* obj, mi_integer index, MI_FPARAM* fp ) { MyUdtData* objdata; mi_double* elements; mi_double* retval; objdata = (MyUdtData*) mi_get_vardata ( (mi_lvarchar*) obj ); retval = (mi_double*) mi_alloc ( sizeof(mi_double) ); elements = (mi_double*) PinMultirepData ( objdata ); *retval = elements[index]; UnpinMultirepData ( objdata ); return retval; } /*--------------------------------------------------------------------------- * MyUdtNumElements (server-callable) * * This is a simple access method which illustrates the use of the udt. * * Returns number of elements in the udt's array. There is no need to * pin the data since we are only accessing the fixed portion of the * udt structure. *--------------------------------------------------------------------------*/ mi_integer MyUdtNumElements ( mi_bitvarying* obj, MI_FPARAM* fp ) { MyUdtData* objdata = (MyUdtData*) mi_get_vardata ( (mi_lvarchar*) obj ); return objdata->nelems; } /*--------------------------------------------------------------------------- * MyUdtAppend (server-callable) * * This is a more complicated method which illustrates how to modify a udt. *--------------------------------------------------------------------------*/ mi_bitvarying* MyUdtAppend ( mi_bitvarying* obj, mi_double* value, MI_FPARAM* fp ) { MyUdtData* old_udt; mi_double* old_elements; mi_integer num_elems; mi_bitvarying* new_udt; mi_pointer dptr; mi_double* new_elements; /* Obtain a pointer to the existing array of values */ old_udt = (MyUdtData*) mi_get_vardata ( (mi_lvarchar*) obj ); old_elements = (mi_double*) PinMultirepData ( old_udt ); num_elems = old_udt->nelems; /* Construct a new Udt, big enough to hold the new, larger array */ new_udt = MyUdtCtor ( num_elems + 1, &dptr ); new_elements = dptr; memcpy ( new_elements, old_elements, num_elems * sizeof(mi_double) ); new_elements[num_elems] = *value; /* Release the old data */ UnpinMultirepData ( old_udt ); return new_udt; } /*--------------------------------------------------------------------------- * MyUdtReplace (server-callable) * * This is a method which illustrates how to modify a udt. * Note that we have to make a new copy of the original udt because if * is a large object, changing the pinned data does not change the * data that is stored in sbspace. There are other, possibly more * efficient ways of doing this using mi_lo_xxx api calls to manipulate * the large object data directly. *--------------------------------------------------------------------------*/ mi_bitvarying* MyUdtReplace ( mi_bitvarying* obj, mi_integer index, mi_double* new_value, MI_FPARAM* fp ) { MyUdtData* old_udt; mi_double* old_elements; mi_bitvarying* new_udt; mi_pointer dptr; mi_double* new_elements; old_udt = (MyUdtData*) mi_get_vardata ( (mi_lvarchar*) obj ); old_elements = (mi_double*) PinMultirepData ( old_udt ); /* Construct a copy of the old Udt */ new_udt = MyUdtCtor ( old_udt->nelems, &dptr ); new_elements = dptr; memcpy ( new_elements, old_elements, old_udt->nelems * sizeof(mi_double) ); /* Modify the specified element */ new_elements[index] = *new_value; /* Release the old data */ UnpinMultirepData ( old_udt ); return new_udt; } /*--------------------------------------------------------------------------- * BuildLargeObject * * Promote a data buffer to a large object; that is, create a large * object and copy the data into it. This is essentially what * mi_large_object_expand does, but this version makes sure the large * object ends up in the correct (i.e. not just the default) sbspace. * * A production version of this routine should do better error handling, * e.g. call mi_db_error_raise with enough information to determine the * point and/or cause of failure. *--------------------------------------------------------------------------*/ MI_LO_HANDLE* BuildLargeObject ( MI_CONNECTION* conn, const mi_char* data, mi_integer len, MI_FPARAM* fp ) { MI_LO_FD lofd; MI_LO_HANDLE* lohndl = NULL; /* make libmi allocate for us */ MI_LO_SPEC* lospec = NULL; /* make libmi allocate for us */ mi_integer status; mi_integer flags = MI_LO_WRONLY; /* Allocate & initialize a lo specification */ status = mi_lo_spec_init ( conn, &lospec ); if ( status != MI_OK ) goto fatal_err; /* Fill in info in the lospec, so that the large object * we are about to create ends up in the correct smartblob space. */ status = mi_lo_colinfo_by_ids ( conn, mi_fp_getrow ( fp ), mi_fp_getcolid ( fp ), lospec ); if ( status != MI_OK ) goto fatal_err; /* Create and write data into a large object */ lofd = mi_lo_expand ( conn, &lohndl, (MI_MULTIREP_DATA*) data, len, flags, lospec ); if ( lofd == MI_ERROR ) goto fatal_err; /* Clean up */ mi_lo_spec_free ( conn, lospec ); mi_lo_close ( conn, lofd ); return lohndl; fatal_err: if ( lospec != NULL ) mi_lo_spec_free ( conn, lospec ); /* Production code should call mi_db_error_raise to abort the UDR */ return NULL; } /*--------------------------------------------------------------------------- * PinMultirepData * * Given a MyUdtData structure, return a pointer to the actual point data. * * A production version of this routine should do better error handling, * e.g. call mi_db_error_raise with enough information to determine the * point and/or cause of failure. *--------------------------------------------------------------------------*/ void* PinMultirepData ( MyUdtData* obj ) { MI_CONNECTION* conn; MI_LO_FD lofd; MI_LO_STAT* lostat = NULL; long size; mi_char* lodata; mi_int8 i8; switch ( obj->mrep_state ) { case MR_Small: /* mrep is the actual data */ return &obj->mrep.mr_data; case MR_Buffered: /* mrep is a pointer to a buffer containing the actual data */ return obj->mrep.mr_data; case MR_Large: /* mrep is a large object handle. Check to see if * data has already been pinned, and if so, return it. */ if (obj->mrep.mr_lo_pin_data != NULL) return obj->mrep.mr_lo_pin_data; break; } /* This code is only reached if mrep is a lo handle AND * the lo data has not yet been pinned. */ conn = mi_open ( NULL, NULL, NULL ); lofd = mi_lo_open ( conn, &obj->mrep.mr_lo, MI_LO_RDONLY ); if ( lofd == MI_ERROR ) goto fatal_err; if ( mi_lo_stat ( conn, lofd, &lostat ) != MI_OK ) goto fatal_err; mi_lo_stat_size ( lostat, &i8 ); mi_free ( lostat ); /* Converte size down to int from int8 */ if ( ifx_int8tolong ( &i8, &size ) != MI_OK ) goto fatal_err; /* Allocate a buffer to hold the large object data, and read all the * data into this buffer. If the object is very large this can be an * expensive operation, so you should use large objects sparingly. */ if ( ( lodata = (mi_char*) mi_alloc ( size ) ) == NULL ) goto fatal_err; if ( mi_lo_read ( conn, lofd, lodata, size ) != size ) { mi_free ( lodata ); goto fatal_err; } good: mi_lo_close ( conn, lofd ); obj->mrep.mr_lo_pin_data = lodata; return obj->mrep.mr_lo_pin_data; fatal_err: if ( lofd != MI_ERROR ) mi_lo_close ( conn, lofd ); /* Production code should call mi_db_error_raise to abort the UDR */ return NULL; } /*--------------------------------------------------------------------------- * UnpinMultirepData * * Unpins the large object if necessary. *--------------------------------------------------------------------------*/ void UnpinMultirepData ( MyUdtData* obj ) { if ( obj->mrep_state == MR_Large && obj->mrep.mr_lo_pin_data != NULL ) { mi_free ( obj->mrep.mr_lo_pin_data ); /* Zero out pointer to pinned data so that multiple unpin * calls will do the right thing. */ obj->mrep.mr_lo_pin_data = NULL; } }