Defining the Input and Output Functions in C

Now you will create the input and output functions for this data type. At this point, you have to decide what your external form will look like. You know that you need to deal with three components: the number of units, an optional exchange rate, and an optional currency name. You want the typical case (units only) to be easy to enter, so you will accept input in any of the following forms:

units units(exchange-rate) units(exchange-rate/currency-name)

If you see a number (and nothing else), assume that you have a number of units of the base currency. If you see a number followed by an open parenthesis, you will expect an exchange rate to follow. If the exchange rate is followed by a slash character, expect a currency name. Of course, we expect a closed parenthesis if we see an open one.

Table 6.1 shows a few valid FCUR external values (assuming that baseCurrencyName is "US$"):

Table 6.1. Sample FCUR Values (in External Form)

External Form

Meaning

'1'

1 U.S. dollar

'1(.5)'

1 unit of unknownCurrencyName with an exchange rate of 0.5

'3(1/US$)'

3 U.S. dollars

'5(.687853/GPB)'

-5 British pounds with an exchange rate of .687853 Pounds per 1 U.S. dollar

'10(7.2566/FRF)'

-10 French francs with an exchange rate of 7.2566 Francs per 1 U.S. dollar

'1.52(1.5702/CA$)'

-1.52 Canadian dollars with an exchange rate of 1.5702 Canadian dollars per 1 U.S. dollar

The input function is named fcur_in (see Listing 6.10), and it converts from external (FCUR) form to internal (fcur) form. This function expects a single parameter: a pointer to a null-terminated string containing the external form of an fcur value.

Listing 6.10. fcur.c (Part 2)

18 /* 19 ** Name: fcur_in() 20 ** 21 ** Converts an fcur value from external form 22 ** to internal form. 23 */ 24 25 PG_FUNCTION_INFO_V1(fcur_in); 26 27 Datum fcur_in(PG_FUNCTION_ARGS) 28 { 29 char * src=PG_GETARG_CSTRING(0); 30 char * workStr = pstrdup( src ); 31 char * units = NULL; 32 char * name = NULL; 33 char * xrate = NULL; 34 fcur * result = NULL; 35 char * endPtr = NULL; 36 37 /* strtok() will find all of the components for us */ 38 39 units = strtok( workStr, "(" ); 40 xrate = strtok( NULL, "/)" ); 41 name = strtok( NULL, ")" ); 42 43 result = (fcur *)palloc( sizeof( fcur )); 44 45 memset( result, 0x00, sizeof( fcur )); 46 47 result->fcur_units = strtod( units, &endPtr ); 48 49 if( xrate ) 50 { 51 result->fcur_xrate = strtod( xrate, &endPtr ); 52 } 53 else 54 { 55 result->fcur_xrate = 1.0; 56 } 57 58 if( name ) 59 { 60 strncpy( result->fcur_name, 61 name, 62 sizeof( result->fcur_name )); 63 } 64 else 65 { 66 strncpy( result->fcur_name, 67 unknownCurrencyName, 68 sizeof( result->fcur_name )); 69 } 70 71 PG_RETURN_POINTER( result ); 72 } 73

Notice that this looks suspiciously similar to the extension functions you saw earlier in this chapter. In particular, fcur_in() returns a Datum and uses PG_FUNCTION_ARGS to declare the parameter list. This similarity exists because fcur_in() is an extension function, so everything that you already know about writing extension functions applies to this discussion as well.

You use the strtok() function (from the C Runtime Library) to parse out the external form. strtok() is a destructive function; it modifies the string that you pass to it. So the first thing you need to do in this function is to make a copy of the input string. Use the pstrdup() function to make the copy. pstrdup() is similar to the strdup() function from the C Runtime Library, except that the memory that holds the copy is allocated using palloc() and must be freed using pfree(). You use pstrdup() to avoid any memory leaks should you forget to clean up after yourself.

Lines 39, 40, and 41 parse the input string into three components. Remember, you will accept input strings in any of the following forms:

units units(exchange-rate) units(exchange-rate/currency-name)

The units component must be a string representing a floating-point number. You will use the strtod() runtime function to convert units into a float4, so the format of the input string must meet the requirements of strtod(). Here is an excerpt from the Linux strtod() man page that describes the required form:

The expected form of the string is optional leading white space as checked by isspace(3), an optional plus (``+'') or minus sign (``-'') followed by a sequence of digits optionally containing a decimal-point character, option- ally followed by an exponent. An exponent consists of an ``E'' or ``e'', followed by an optional plus or minus sign, followed by a non-empty sequence of digits. If the locale is not "C" or "POSIX", different formats may be used.

The optional exchange-rate component is also converted to a float4 by strtod().

The currency-name component is simply a three-character string. Values such as "US$" (U.S. dollar),"GPB" (British pound), and "CA$" (Canadian dollar) seem reasonable. In your sample data type, you won't do any validation on this string. In a real-world implementation, you would probably want to match the currency name with a table of valid (and standardized) spellings.

The first call to strtok() returns a null-terminated string containing all characters up to (but not including) the first ( in workStr. If workStr doesn't contain a ( character, units will contain the entire input string. The second call to strtok() picks out the optional exchange-rate component. The final call to strtok() picks out the optional currency-name.

After you have tokenized the input string into units, exchange rate, and currency name, you can allocate space for the internal form at line 43. Notice that palloc() is used here.

The rest of this function is pretty simple. You use strtod() to convert the units and exchange rate into the fcur structure. If the user didn't provide you with an exchange rate, assume that it must be 1.0. You finish building the fcur structure by copying in the first three characters of the currency name, or unknownCurrencyName if you didn't find a currency name in the input string.

Line 71 returns the Datum to the caller.

That's pretty simple! Of course, I omitted all the error-checking code that you would need in a real-world application.

Now, let's look at the output function. fcur_out(), shown in Listing 6.11, converts an fcur structure from internal to external form.

Listing 6.11. fcur.c (Part 3)

74 /* 75 ** Name: fcur_out() 76 ** 77 ** Converts an fcur value from internal form 78 ** to external form. 79 */ 80 81 PG_FUNCTION_INFO_V1(fcur_out); 82 83 Datum fcur_out(PG_FUNCTION_ARGS) 84 { 85 fcur * src=(fcur *)PG_GETARG_POINTER( 0 ); 86 char * result; 87 char work[16+sizeof(src->fcur_name)+16+4]; 88 89 sprintf( work, "%g(%g/%s)", 90 src->fcur_units, 91 src->fcur_xrate, 92 src->fcur_name ); 93 94 result = (char *)palloc( strlen( work ) + 1 ); 95 96 strcpy( result, work ); 97 98 PG_RETURN_CSTRING( result ); 99 100 } 101

This function is much shorter than the input function. That's typically the case because your code has far fewer decisions to make.

You format the fcur components into a work buffer at lines 89 through 92: sprintf() takes care of all the grunt work. Notice that you are formatting into an array of characters large enough to hold the largest result that you can expect (two 16-digit numbers, a function name, two parentheses, a slash, and a null terminator). Some of you might not like using a fixed-size buffer with sprintf(); use snprintf() if you have it and you are worried about buffer overflows.

After you have a formatted string, use palloc() to allocate the result string. (In case you were wondering, you format into a temporary buffer first so that you can allocate a result string of the minimum possible size.) At line 96, you copy the temporary string into the result string and then return that string at line 98.

I should point out an important consideration about the input and output functions that you have just written. It's very important that the format of the string produced by the output function match the format understood by the input function. When you back up a table using pg_dump, the archive contains the external form of each column. When you restore from the archive, the data must be converted from external form to internal form. If they don't match, you won't be able to restore your data.

Категории