Example UDF (Non-CUDA) - Sum of Squares

The following is a complete example, using the Java UDF API, of a non-CUDA UDF that takes a list of input tables and corresponding output tables (must be the same number) and, for each record of each input table, sums the squares of input table columns and saves the result to the corresponding output table column; i.e.

in.a2 + in.b2 + ... + in.n2 -> out.a

Prerequisites

This setup assumes the UDF is being developed on the Kinetica host (or head node host, if a multi-node Kinetica cluster), the Python database API is available at /opt/gpudb/api/python, and the Java UDF API is available at /opt/gpudb/udf/api/java (the Java UDF API compiled jar, kinetica-proc-api-7.1.0.0-jar-with-dependencies.jar, should already be available). You'll also need Maven to compile the example UDF JAR.

Download & Run

This example has been archived (click to download) for your convenience and will contain the following files:

  • sos/pom.xml: used by Maven to compile the example UDF into a JAR that is executed in udf_sos_java_exec.py
  • sos/udf_sos_java_init.py: creates the schema and input & output tables and loads test data
  • sos/udf_sos_java_exec.py: creates & executes the UDF
  • sos/src/main/java/UdfSosJavaProc.java: implements the UDF itself

Note

All commands should be run as the gpudb user.

After copying the archive to a gpudb-accessible directory on the Kinetica head node and unzipping it, the example can be run as follows, optionally specifying the database host and a username & password to the Python scripts:

1
2
3
4
$ cd sos/
$ mvn clean package
$ /opt/gpudb/bin/gpudb_python output/udf_sos_java_init.py [--host <kinetica-host> [--username <kinetica-user> --password <kinetica-pass>]]
$ /opt/gpudb/bin/gpudb_python output/udf_sos_java_exec.py [--host <kinetica-host> [--username <kinetica-user> --password <kinetica-pass>]]

Important

When working on your own UDFs, ensure the the Kinetica Java UDF API is not bundled with your UDF JAR; otherwise, there could be a compilation target platform conflict with the UDF API on the Kinetica server.

The results of the run can be checked via Kinetica Administration Application (GAdmin). There should exist two tables within the udf_example_java schema, udf_sos_in_table & udf_sos_out_table, each holding 10,000 records; the former containing pairs of numbers and the latter containing the sums of squares of those numbers. Each table will carry an id, which can be used to associate input values to output sums.

To verify the existence of the tables, in GAdmin, click Data > Tables. Both tables should appear in the udf_example_java schema, each with 10,000 records.

To verify the calculations, click Query > KiSQL. Enter the following query into the SQL Statement box:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
SELECT
   sos_in.id,
   STRING(x1) || '^2 + ' || STRING(x2) || '^2 = ' || STRING(y) AS "Equation"
FROM
   udf_example_java.udf_sos_in_table sos_in,
   udf_example_java.udf_sos_out_table sos_out
WHERE
   sos_in.id = sos_out.id
ORDER BY
   id;

The Query Result box should show each of the 10,000 calculations made.

Execution Detail

While the example UDF itself can run against multiple tables, the example run will use a single schema-qualified table, udf_example_java.udf_sos_in_table, as input and a matching schema-qualified table, udf_example_java.udf_sos_out_table, for output.

The input table will contain two float columns and be populated with 10,000 pairs of randomly-generated numbers. The output table will contain one float column that will hold the sums calculated by the UDF. Both tables will also contain an int column that is the calculation identifier, allowing the input data to be matched up with the output data after the UDF has run.

Note

The UDF will assume the first column of the input table, as defined in the original table creation process, is the identifier field. All of the remaining columns after the first will be used in the sum-of-squares calculation.

The UDF will calculate the sum of the squares of each of the 10,000 pairs of numbers and insert into the output table the corresponding 10,000 sums.

udf_sos_java_init.py

This initialization script creates the schema, input & output tables, and populates the input data using the standard Kinetica Python API, all outside of the UDF execution framework.

Several aspects of the initialization process are noteworthy:

  • The external database connection, indicative of the use of the standard Kinetica Python API--the UDF will not have this as it runs within the database:
1
kinetica = gpudb.GPUdb(host=['http://' + args.host + ':9191'], username=args.username, password=args.password)
  • Schema, input, and output table creation:
1
kinetica.create_schema(SCHEMA, options=OPTION_NO_CREATE_ERROR)
1
2
3
4
5
columns = []
columns.append(gpudb.GPUdbRecordColumn("id", gpudb.GPUdbRecordColumn._ColumnType.INT, [gpudb.GPUdbColumnProperty.PRIMARY_KEY, gpudb.GPUdbColumnProperty.INT16]))
columns.append(gpudb.GPUdbRecordColumn("x1", gpudb.GPUdbRecordColumn._ColumnType.FLOAT))
columns.append(gpudb.GPUdbRecordColumn("x2", gpudb.GPUdbRecordColumn._ColumnType.FLOAT))
input_table = gpudb.GPUdbTable(columns, INPUT_TABLE, db = kinetica)
1
2
3
4
columns = []
columns.append(gpudb.GPUdbRecordColumn("id", gpudb.GPUdbRecordColumn._ColumnType.INT, [gpudb.GPUdbColumnProperty.PRIMARY_KEY, gpudb.GPUdbColumnProperty.INT16]))
columns.append(gpudb.GPUdbRecordColumn("y", gpudb.GPUdbRecordColumn._ColumnType.FLOAT))
gpudb.GPUdbTable(columns, OUTPUT_TABLE, db = kinetica)

UdfSosJavaProc.java

This is the UDF itself. It uses the Kinetica Java UDF API to compute the sums of squares of input table columns and output those sums to an output table. It runs within the UDF execution framework, and as such, is not called directly--instead, it is registered and launched by udf_sos_java_exec.py.

Noteworthy in the UDF are the following:

  • The initial call to ProcData() to access the database:
1
ProcData procData = ProcData.get();
  • The size of the output table must be specified before writing to it:
1
2
3
long recordCount = inputTable.getSize();

outputTable.setSize(recordCount);
  • The final call to complete() to mark the process as finished and ready for clean-up:
1
procData.complete();

udf_sos_java_exec.py

The execution script uses the standard Kinetica Python API to register the UDF in the database and then execute it.

The registration step associates a name with the UDF execution code contained in UdfSosJavaProc.java, the command ( java ) and arguments (the name of the compiled proc JAR) to use to run it, and that it will run in distributed mode.

1
response = kinetica.create_proc(proc_name, 'distributed', files, 'java', ['-jar', proc_jar_file], {})

The execution step invokes the UDF by name, passing in the input & output table names against which the UDF will execute.

1
response = kinetica.execute_proc(proc_name, {}, {}, [INPUT_TABLE], {}, [OUTPUT_TABLE], {})