MaxCompute user-defined aggregate functions (UDAFs) and user-defined table-valued functions (UDTFs) use the Resolve annotation to declare a function's input and output types. By default, the parameter list is fixed and cannot be overloaded. This topic describes how to use the extended Resolve syntax to accept dynamic parameters—variable-length or any-type inputs—and to return a variable number of columns from a UDTF.
How the Resolve annotation works
The Resolve annotation defines the input and output types for a UDAF or UDTF at declaration time. The following example declares a UDTF that accepts a BIGINT parameter and returns a DOUBLE value:
@com.aliyun.odps.udf.annotation.Resolve("BIGINT->DOUBLE")
public class UDTFClass extends UDTF {
...
}Because the signature is fixed, the same class cannot be overloaded to accept different parameter types. To handle variable-length or any-type inputs, use the extended syntax described in the following sections.
Extended syntax overview
The Resolve annotation supports three extensions. The following table summarizes each extension, its valid position, and its effect:
| Syntax | Valid position | Effect |
|---|---|---|
* | Input parameter list | Accepts any number of additional parameters of any type after any preceding fixed parameters |
any | Input parameter list | Accepts any single type at the marked position |
* | Return value list (UDTF only) | Returns any number of STRING columns; the count is determined by the number of aliases in the SQL as clause at call time |
Asterisk (*) in input parameters
Use * in the input parameter list to indicate that an input parameter can be of any length and type.
// First param is DOUBLE; remaining params are variable-length and any type
@Resolve("double,*->string")Inside the process method, inspect the Object[] input array at runtime to determine the count and types of the variable-length parameters. Apply the same management pattern as printf in C: examine each element's position and type in the array.
any in input parameters
Use any in a parameter list to indicate that parameters of all types are valid.
// First param is DOUBLE; second param accepts any type
@Resolve("double,any->string")Use any when the type at a given position is unknown at declaration time. Note that any cannot be used in return values or subtypes of complex data types, such as ARRAY.
Asterisk (*) in return values (UDTF only)
Place * in the return value list to declare that the UDTF returns any number of STRING columns. The exact column count is determined at call time by the number of aliases in the SQL as clause.
// Returns a DOUBLE column followed by any number of STRING columns
@Resolve("ANY,ANY->DOUBLE,*")When this UDTF is called as UDTF(x, y) as (a, b, c), the type mapping is:
a— DOUBLE (first declared return type)b,c— STRING (from*)
The forward method must output an array whose length matches the total number of aliases. In this example, forward must pass an array of three elements.
forward array length, a runtime error occurs—not a compile-time error.Limitations
| Constraint | Details |
|---|---|
any in return values | Not supported. any is only valid in input parameter lists. |
any in complex type subtypes | Not supported. For example, any cannot be used as the element type of ARRAY. |
| Variable-length return columns must be STRING | When * appears in a UDTF's return value list, all columns from * are STRING. No other type is allowed for those positions. |
Alias count must match forward array length | The number of aliases in the SQL as clause determines the array length passed to forward. A mismatch causes a runtime error, not a compile-time error. |
| UDAF return value count | UDAFs always return exactly one value. The * return value extension applies only to UDTFs. |
Example: parsing JSON with variable keys
The following JsonTuple class accepts a JSON string and any number of key names, then returns the extracted values for those keys.
import com.aliyun.odps.udf.UDFException;
import com.aliyun.odps.udf.UDTF;
import com.aliyun.odps.udf.annotation.Resolve;
import org.json.JSONException;
import org.json.JSONObject;
// First param: JSON string. Remaining params: keys to extract.
// Returns: error message (STRING), followed by extracted values (STRING).
@Resolve("STRING,*->STRING,*")
public class JsonTuple extends UDTF {
private Object[] result = null;
@Override
public void process(Object[] input) throws UDFException {
if (result == null) {
result = new Object[input.length];
}
try {
JSONObject obj = new JSONObject((String) input[0]);
for (int i = 1; i < input.length; i++) {
// Variable-length return values must be STRING.
result[i] = String.valueOf(obj.get((String) input[i]));
}
result[0] = null; // No error
} catch (JSONException ex) {
for (int i = 1; i < result.length; i++) {
result[i] = null;
}
result[0] = ex.getMessage(); // First column returns the error message
}
forward(result);
}
}The output column count equals the input parameter count. The first alias receives the error message (or null on success), and the remaining aliases receive the extracted JSON values in key order.
SQL usage
Register JsonTuple as my_json_tuple, then call it as follows:
-- Configure the number of output aliases based on that of input parameters.
SELECT my_json_tuple(json, 'a', 'b') as exceptions, a, b FROM jsons;
-- The variable-length part can have no columns.
SELECT my_json_tuple(json) as exceptions, a, b FROM jsons;The following call results in a runtime error because the alias count does not match the actual number of parameters. This error is not returned during compilation.
-- Runtime error: alias count does not match parameter count
SELECT my_json_tuple(json, 'a', 'b') as exceptions, a, b, c FROM jsons;What's next
If the Resolve annotation extensions do not meet your requirements, implement your logic using user-defined types (UDTs). For details, see Overview.
For Python-based implementations, see: