Create the C++ file for the UDF

To begin, use any text editor to create your C++ file. The file name must have a .cpp extension. You might want to create a directory such as /home/nz/udx_files as your area for UDX code files.

Your C++ file must include the udxinc.h header file, which contains the required declarations for user-defined functions and processing on the SPUs.

#include "udxinc.h"
In addition, make sure that you declare any of the standard C++ library header files that your function might require. If your UDF requires any user-defined shared libraries, make sure that you note the name of the libraries because you will need them when you register the UDF in the database. For example:
#include "udxinc.h"
#include <string.h>
Note: User-defined shared libraries must exist in the database before you can register the UDF and specify those libraries as dependencies. You can register the UDF without specifying any library dependencies, and after the libraries are added, use the ALTER FUNCTION command to update the UDF definition with the correct dependencies. For more information about user-defined shared libraries, see Create a user-defined shared library.
The UDX classes and functions for API version 2 are defined in a namespace called nz::udx_ver2. (The API version 1 UDXs use the nz::udx namespace.) Your C++ program must reference the correct namespace. For example:
#include "udxinc.h"
#include <string.h>
using namespace nz::udx_ver2;
Note: This section uses udx_ver2 as the default namespace for the examples that follow. The sections note the differences with UDX version 1, and Sample user-defined functions and aggregates reference contains examples of version 1 and version 2 definitions. You can continue to create UDX version 1 functions and new version 2 functions; both functions run on release 6.0.x systems. However, the version 1 functions work on Netezza Performance Server release 5.0.x and later systems and thus might be more portable for your Netezza Performance Server systems.
To implement a UDF, you create a class object that is derived from the Udf base class. Continuing the customername example:
#include "udxinc.h"
#include <string.h>

using namespace nz::udx_ver2;
class CCustomerName: public nz::udx_ver2::Udf
{
public:
};

Each UDF must implement the following two methods:

  • The instantiate() method is called by the runtime engine to create the object dynamically. The static implementation must be outside the class definition. In UDX version 2, the instantiate method takes one argument (UdxInit *pInit), which enables access to the memory specification, the log setting, and the UDX environment (see UDX environment). The constructor must take a UdxInit object as well and pass it to the base class constructor. The instantiate method creates an object of the derived class type by using the new operator and returns it (as base class type Udf) to the runtime engine. The runtime engine deletes the object when it is no longer needed. An example of the instantiate method for API version 2 follows:
       #include "udxinc.h"
    #include <string.h>
    
    using namespace nz::udx_ver2;
    
    class CCustomerName: public nz::udx_ver2::Udf
    {
    public:
        CCustomerName(UdxInit *pInit) : Udf(pInit)
    {
    }
        static nz::udx_ver2::Udf* instantiate(UdxInit *pInit);
    };
    nz::udx_ver2::Udf* CCustomerName::instantiate(UdxInit *pInit)
    {
            return new CCustomerName(pInit);
    }
  • The evaluate() method is called once for each row of data during execution.
       #include "udxinc.h"
    #include <string.h>
    
    using namespace nz::udx_ver2;
    
    class CCustomerName: public nz::udx_ver2::Udf
    {
    public:
        CCustomerName(UdxInit *pInit) : Udf(pInit)
    {
    }
        static nz::udx_ver2::Udf* instantiate(UdxInit *pInit);
        virtual nz::udx_ver2::ReturnValue evaluate()
        {
            // Code to be inserted here
        }
    };
    
    nz::udx_ver2::Udf* CCustomerName::instantiate(UdxInit *pInit)
    {
        return new CCustomerName(pInit);
    }
    

You can implement constructors and destructors as necessary. In API Version 1, constructors are optional. In API version 2, constructors are required; you must specify a constructor even if it only invokes the base class constructor, as in the previous example. Some common things to include in constructors are memory reservation routines (since new and delete are relatively expensive), and setting up any structures that are needed for computation (for example, setting up a matrix for encryption routines).

For the customername example, the UDF takes a string and returns the integer 1 if the string starts with “Customer A”, otherwise it returns the integer 0. The code for the evaluate method follows:
virtual nz::udx_ver2::ReturnValue evaluate()
    {
        StringArg *str;
        str = stringArg(0);              // 4
        int lengths = str->length;                  // 5
        char *datas = str->data;                    // 6
        int32 retval = 0;
        if (lengths >= 10)
            if (memcmp("Customer A",datas,10) == 0)
                            retval = 1;
        NZ_UDX_RETURN_INT32(retval);                 // 11
    }

In the sample program, line 4 declares and uses a StringArg structure to pass the argument to the UDF. Arguments to UDFs are retrieved by using functions such as StringArg. If this UDF took a second string argument, the second argument would be referenced by StringArg(1). For a complete list of argument types that are supported and their associated helper functions, see Data type helper API reference. Lines 5 and 6 extract the length and character pointer (char*) from the StringArg structure.

Note: The sample program uses memcmp (not strcmp) because StringArg structures are not null-terminated (\0) in user-defined functions or aggregates. Therefore, strcmp, strcpy, strlen, atol, and other functions that depend on the presence of a null terminator do not work. If you need to use those functions, you must copy the string into a buffer and manually append the null terminator.

Line 11 uses a UDX macro to return the computed value to the Netezza Performance Server engine. The NZ_UDX_RETURN_INT32 macro helps to confirm that the return value is of the expected type. For a list of the available return macros, see UDX return value macros.

To use the function, you must compile and register it as an available function on the Netezza Performance Server system.