AnalyticDB for MySQL supports the REMOTE_CALL() function, which lets you invoke custom user-defined functions (UDFs) hosted in Function Compute directly from SQL. Only scalar UDFs are supported.
Prerequisites
Before you begin, make sure you have:
An AnalyticDB for MySQL cluster running kernel version 3.2.1.0 or later
The cluster and your Function Compute service in the same region
For faster cold-start times, select Python, Go, or Node.js as the Runtime Environment when creating the UDF.
How it works
Function Compute acts as the remote function server. When REMOTE_CALL() is invoked in a SQL statement, AnalyticDB for MySQL sends data to Function Compute in JSON format and returns the result to the client.
A client submits a SQL statement to AnalyticDB for MySQL.
After
REMOTE_CALL()is called, AnalyticDB for MySQL sends data to Function Compute in JSON format.Function Compute processes the data using your UDF.
Function Compute returns the result to AnalyticDB for MySQL in JSON format.
AnalyticDB for MySQL returns the final result to the client.
Syntax
remote_call('returnType', 'func_name', ['{external_config}'|NULL], X1, X2, ..., Xn)Parameters
| Parameter | Description |
|---|---|
returnType | The return value data type. Valid values: BOOLEAN, DOUBLE, VARCHAR, INTEGER, TINYINT, BIGINT, TIME, DATE, TIMESTAMP, DATETIME. |
func_name | The name of the UDF in Function Compute. For Function Compute 3.0, specify the function name only. For Function Compute 2.0, use the format serviceName$functionName. Log in to the Function Compute console to check the version. |
external_config | NULL | Extended configuration in JSON format. Specify NULL if not needed. Query-level settings override global settings configured with SET ADB_CONFIG. For details, see Extended parameters for external_config. |
X1...Xn | The input parameters. Valid values: BOOLEAN, DOUBLE, VARCHAR, INTEGER, TINYINT, BIGINT, TIME, DATE, TIMESTAMP, DATETIME. |
Extended parameters for external_config
Set external_config parameters at the query level, or configure them globally using SET ADB_CONFIG. Query-level settings take precedence over global settings.
| Global parameter | Query-level parameter | Required | Description |
|---|---|---|---|
XIHE_REMOTE_CALL_SERVER_ENDPOINT | endpoint | Yes | The internal service endpoint of Function Compute. For details, see Service endpoints. |
XIHE_REMOTE_CALL_SERVER_AK | — | Conditional | The AccessKey ID of the Alibaba Cloud account or Resource Access Management (RAM) user with permissions on Function Compute. Required for Data Warehouse Edition clusters. For Enterprise Edition, Basic Edition, and Data Lakehouse Edition clusters, Security Token Service (STS) handles authentication automatically for same-account access; cross-account access requires the AccessKey ID. Can only be set as a global parameter. For details, see Accounts and permissions. |
XIHE_REMOTE_CALL_SERVER_SK | — | Conditional | The AccessKey secret of the Alibaba Cloud account or RAM user. Same conditions as XIHE_REMOTE_CALL_SERVER_AK. Can only be set as a global parameter. |
XIHE_REMOTE_CALL_COMPRESS_ENABLED | compressed | No | Specifies whether to compress the data payload in GZIP format before sending it to Function Compute. Default: true. |
XIHE_REMOTE_CALL_MAX_BATCH_SIZE | max_batch_size | No | The maximum number of rows sent to Function Compute per batch. Default: no limit. A smaller batch size reduces CPU and memory pressure on Function Compute but increases query time. |
XIHE_REMOTE_CALL_SERVER_AK and XIHE_REMOTE_CALL_SERVER_SK can only be configured as global parameters using SET ADB_CONFIG.
Write a Function Compute handler
Your UDF must accept input and return results in the JSON format that REMOTE_CALL() uses.
Example: Java handler
The following example implements ConcactNumberWithCompress, which takes two integers a and b and returns the string "a&b&1".
public class App
implements StreamRequestHandler, FunctionInitializer
{
public static final Logger log = Logger.getLogger(App.class.getName());
public void initialize(Context context) throws IOException {
// TODO
}
@Override
public void handleRequest(
InputStream inputStream, OutputStream outputStream, Context context)
throws IOException
{
// Step 1: Decompress the input.
// REMOTE_CALL() compresses data with GZIP by default (compressed=true).
// If you set compressed=false in external_config, skip this step.
InputStream decompressedInput = tryUnCompress(inputStream);
JSONObject response = new JSONObject();
try {
// Step 2: Parse the JSON body sent by AnalyticDB for MySQL.
// The body has the structure: {"rowCount": N, "compressed": true/false, "data": [[...], ...]}
JSONObject requestJson = JSONObject.parseObject(IOUtils.toString(decompressedInput));
if (requestJson.containsKey("data")) {
JSONArray result = new JSONArray();
// Step 3: Iterate over the rows in the "data" array.
// Each row is a JSON array whose columns match the X1...Xn parameter order in REMOTE_CALL().
// This function expects two integers: X1 (index 0) and X2 (index 1).
JSONArray data = requestJson.getJSONArray("data");
for (int i = 0; i < data.size(); i++) {
JSONArray row = data.getJSONArray(i);
if (row.size() == 2) {
result.add(testFunc(row.getInteger(0), row.getInteger(1)));
} else {
throw new RuntimeException("row size is not 2");
}
}
// Step 4: Build the response.
response.put("result", result);
response.put("success", true);
response.put("message", "");
} else {
response.put("success", false);
response.put("message", "no data inside");
}
} catch (Exception e) {
log.info("error happened" + e.getMessage());
response.put("success", false);
response.put("message", e.getMessage());
}
// Step 5: Compress the response.
// REMOTE_CALL() expects a GZIP-compressed response when compressed=true (the default).
// If you set compressed=false in external_config, remove this compression step.
outputStream.write(tryCompress(response.toJSONString().getBytes()));
}
private String testFunc(int a, int b) {
// Concatenate a, b, and 1 with ampersands.
return String.valueOf(a) + '&' + b + '&' + 1;
}
public static byte[] tryCompress(byte[] bytes) {
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
try {
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteOutputStream);
gzipOutputStream.write(bytes);
gzipOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return byteOutputStream.toByteArray();
}
public static InputStream tryUnCompress(InputStream inputStream) throws IOException {
return new GZIPInputStream(inputStream);
}
}Examples
Call with query-level config
Pass the Function Compute endpoint directly in the external_config parameter:
-- Concatenate 1 and 2 with an ampersand
SELECT remote_call(
'varchar',
'ConcactNumberWithCompress',
'{endpoint:"1234567890000****.cn-zhangjiakou-internal.fc.aliyuncs.com"}',
1, 2
);
-- Result: 1&2&1Disable GZIP compression and set a batch size limit:
SELECT remote_call(
'varchar',
'ConcactNumberWithCompress',
'{endpoint:"1234567890000****.cn-zhangjiakou-internal.fc.aliyuncs.com",compressed:false,max_batch_size:5000000}',
3, 4
);
-- Result: 3&4&1Call with global config
Configure global parameters once using SET ADB_CONFIG, then omit external_config from subsequent calls:
SELECT remote_call('varchar', 'ConcactNumberWithCompress', null, 5, 6);
-- Result: 5&6&1Troubleshooting
java.util.zip.ZipException: Not in GZIP format
This error has two common causes:
Decompression mismatch on input: AnalyticDB for MySQL compresses data into GZIP format and sends it to Function Compute, but the user-defined function created in Function Compute does not contain decompression code. As a result, Function Compute cannot parse the data.
Compression mismatch on output: AnalyticDB for MySQL sends uncompressed data to Function Compute. After processing, Function Compute compresses the result using GZIP format and returns it to AnalyticDB for MySQL. As a result, AnalyticDB for MySQL cannot parse the data.
parse remote_call config error
The external_config JSON is malformed. Check the syntax of the external_config value in your SQL statement, correct it, and run the query again.