C++ language scalar function

The following example shows a simple scalar function that sums a set of numbers. This example starts from the beginning to construct a simple AE. It uses the following file name: applyopcpp.cpp

Code

  1. Pull in the C++ interface. This can typically be accomplished by using:
    #include <nzaefactory.h>
  2. Write a main. Determine an API to use, in this case a function. This can be achieved either by getting a local AE connection or a remote AE connection. This example uses a local AE connection. Two interfaces can be used. One uses NzaeFactory and the other uses NzaeApiGenerator. In this example, use the generator class as it is simpler and can also be used for remote mode. The program should appear similar to:
    include <nzaefactory.hpp>
    using namespace nz::ae;
    int main(int argc, char * argv[])
    {
    NzaeApiGenerator helper;
    NzaeApi &api = helper.getApi(NzaeApi::FUNCTION);
    return 0;
    }
    This retrieves an NzaeApi reference that contains a usable NzaeFunction object.
  3. When the function is available, add a stub for calling the function logic:
    #include <nzaefactory.hpp>
    using namespace nz::ae;
    static int run(nz::ae::NzaeFunction *aeFunc);
    int main(int argc, char * argv[])
    {
    NzaeApiGenerator helper;
    NzaeApi &api = helper.getApi(NzaeApi::FUNCTION);
    run(api.aeFunction);
    return 0;
    }
    static int run(NzaeFunction *aeFunc)
    {
    return 0;
    }
  4. Implement the function.

    There are two ways of implementing a function. Either use the NzaeFunction object interface directly or implement an NzaeFunctionMessageHandler-derived class that provides a simpler interface. In this example, the message handler is used. The second method is covered in a separate example. The message handler can be used only for functions that return one row of output per input. Also, the handler covers some error handling details automatically.

    #include <nzaefactory.hpp>
    using namespace nz::ae;
    static int run(nz::ae::NzaeFunction *aeFunc);
    int main(int argc, char * argv[])
    {
    NzaeApiGenerator helper;
    NzaeApi &api = helper.getApi(NzaeApi::FUNCTION);
    run(api.aeFunction);
    return 0;
    }
    class MyHandler : public NzaeFunctionMessageHandler
    {
    public:
    enum OperatorEnum
    {
    OP_ADD = 1,
    OP_MULT = 2
    } op;
    void evaluate(NzaeFunction& api, NzaeRecord &input, NzaeRecord &result) {
    double res = 0;
    NzaeField &field = input.get(0);
    if (field.isNull())
    throw NzaeException("first input column may not be null");
    NzaeStringField &sf = (NzaeStringField&) input.get(0);
    std::string strop = (std::string)sf;
    if (strop == "+")
    op = OP_ADD;
    else if (strop == "*") {
    op = OP_MULT;
    res = 1;
    }
    else
    throw NzaeException(NzaeException::format("unexpected \
    operator %s", strop.c_str()));
    for (int i=1 ; i < input.numFields(); i++) {
    NzaeField &inf = input.get(i);
    if (inf.isNull())
    continue;
    double temp = 0;
    bool ignore = false;
    switch (inf.type()) {
    case NzaeDataTypes::NZUDSUDX_INT32:
    {
    NzaeInt32Field &sf = (NzaeInt32Field&)input.get(i);
    temp = (int32_t)sf;
    break;
    }
    default:
    ignore = true;
    // ignore
    break;
    }
    if (!ignore) {
    if (op == OP_ADD)
    res += temp;
    else
    res *= temp;
    }
    }
    NzaeDoubleField &f = (NzaeDoubleField&)result.get(0);
    f = res;
    }
    };
    static int run(NzaeFunction *aeFunc)
    {
    aeFunc->run(new MyHandler());
    return 0;
    }

    In this example, the run function instantiates the handler object, and then invokes the run method of the function class on the handler. The handler must implement an evaluate method, which takes a reference to the function as well as references to the input record and the output record. For normal functions, there is a single column in the output record. The evaluate can then retrieve fields from the input record and use that to set the output field. This code provides a basic, non-error-checking function that should take as the first argument either the string plus sign (+) or the string asterisk (*) and for subsequent arguments int32s. It then either adds or multiplies the int32s, based on the specified argument value, and returns the result as a double.

  5. The final code preparation step adds error checking and support for additional data types.
    #include <nzaefactory.hpp>
    using namespace nz::ae;
    static int run(nz::ae::NzaeFunction *aeFunc);
    int main(int argc, char * argv[])
    {
    NzaeApiGenerator helper;
    NzaeApi &api = helper.getApi(NzaeApi::FUNCTION);
    run(api.aeFunction);
    return 0;
    }
    class MyHandler : public NzaeFunctionMessageHandler
    {
    public:
    MyHandler() {
    m_Once = false;
    }
    enum OperatorEnum
    {
    OP_ADD = 1,
    OP_MULT = 2
    } op;
    void doOnce(NzaeFunction& api) {
    const NzaeMetadata& meta = api.getMetadata();
    if (meta.getOutputColumnCount() != 1 ||
    meta.getOutputType(0) != NzaeDataTypes::NZUDSUDX_DOUBLE) {
    throw NzaeException("expecting one output column of type double");
    }
    if (meta.getInputColumnCount() < 1) {
    throw NzaeException("expecting at least one input column");
    }
    if (meta.getInputType(0) != NzaeDataTypes::NZUDSUDX_FIXED &&
    meta.getInputType(0) != NzaeDataTypes::NZUDSUDX_VARIABLE) {
    throw NzaeException("first input column expected to be a string \
    type");
    }
    m_Once = true;
    }
    void evaluate(NzaeFunction& api, NzaeRecord &input, NzaeRecord &result) {
    if (!m_Once)
    doOnce(api);
    double res = 0;
    NzaeField &field = input.get(0);
    if (field.isNull())
    throw NzaeException("first input column may not be null");
    NzaeStringField &sf = (NzaeStringField&) input.get(0);
    std::string strop = (std::string)sf;
    if (strop == "+")
    op = OP_ADD;
    else if (strop == "*") {
    op = OP_MULT;
    res = 1;
    }
    else
    throw NzaeException(NzaeException::format("unexpected operator \
    %s", strop.c_str()));
    for (int i=1 ; i < input.numFields(); i++) {
    NzaeField &inf = input.get(i);
    if (inf.isNull())
    continue;
    double temp = 0;
    bool ignore = false;
    switch (inf.type()) {
    case NzaeDataTypes::NZUDSUDX_INT8:
    {
    NzaeInt8Field &sf = (NzaeInt8Field&)input.get(i);
    temp = (int8_t)sf;
    break;
    }
    case NzaeDataTypes::NZUDSUDX_INT16:
    {
    NzaeInt16Field &sf = (NzaeInt16Field&)input.get(i);
    temp = (int16_t)sf;
    break;
    }
    case NzaeDataTypes::NZUDSUDX_INT32:
    {
    NzaeInt32Field &sf = (NzaeInt32Field&)input.get(i);
    temp = (int32_t)sf;
    break;
    }
    case NzaeDataTypes::NZUDSUDX_INT64:
    {
    NzaeInt64Field &sf = (NzaeInt64Field&)input.get(i);
    temp = (int64_t)sf;
    break;
    }
    case NzaeDataTypes::NZUDSUDX_FLOAT:
    {
    NzaeFloatField &sf = (NzaeFloatField&)input.get(i);
    temp = (float)sf;
    break;
    }
    case NzaeDataTypes::NZUDSUDX_DOUBLE:
    {
    NzaeDoubleField &sf = (NzaeDoubleField&)input.get(i);
    temp = (double)sf;
    break;
    }
    case NzaeDataTypes::NZUDSUDX_NUMERIC32:
    case NzaeDataTypes::NZUDSUDX_NUMERIC64:
    case NzaeDataTypes::NZUDSUDX_NUMERIC128:
    {
    NzaeNumericField &sf = (NzaeNumericField&)input.get(i);
    temp = (double)sf;
    break;
    }
    default:
    ignore = true;
    // ignore
    break;
    }
    if (!ignore) {
    if (op == OP_ADD)
    res += temp;
    else
    res *= temp;
    }
    }
    NzaeDoubleField &f = (NzaeDoubleField&)result.get(0);
    f = res;
    }
    bool m_Once;
    };
    static int run(NzaeFunction *aeFunc)
    {
    aeFunc->run(new MyHandler());
    return 0;
    }

    This code ensures the correct number and types of input arguments and the correct number and type of output arguments. It supports all of the integer types that are float, double and numeric. After the code is complete, it must be compiled and registered.

Compilation

Compile the code as follows:
$NZ_EXPORT_DIR/ae/utilities/bin/compile_ae --language cpp --template compile \
--exe applyopcpp --compargs "-g -Wall" --linkargs "-g" applyopcpp.cpp \
--version 3

The arguments specify that you are using the C++ language, version 3, with the template compile, and creating the executable applyopcpp. Also, the compiler and linker use the provided additional argument so that the applyopcpp executable has debug symbols.

Registration

When compiled, register the code:
$NZ_EXPORT_DIR/ae/utilities/bin/register_ae --sig "applyop_cpp(VARARGS)" \
--return "double" --language cpp --template udf --exe applyopcpp \
--version 3

Running

You can now run the AE in SQL:
SELECT applyop_cpp('+',1,2);
APPLYOP_CPP
-------------
3
(1 row)
Note that in the code you validate the types by using doOnce and send an error by using NzaeException. The following example triggers an error:
SELECT applyop_cpp('-',1,2);
ERROR: unexpected operator -