All Products
Search
Document Center

AnalyticDB:UDFs

Last Updated:Mar 28, 2026

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

  • A UDF created in Function Compute

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.

image
  1. A client submits a SQL statement to AnalyticDB for MySQL.

  2. After REMOTE_CALL() is called, AnalyticDB for MySQL sends data to Function Compute in JSON format.

  3. Function Compute processes the data using your UDF.

  4. Function Compute returns the result to AnalyticDB for MySQL in JSON format.

  5. AnalyticDB for MySQL returns the final result to the client.

Syntax

remote_call('returnType', 'func_name', ['{external_config}'|NULL], X1, X2, ..., Xn)

Parameters

ParameterDescription
returnTypeThe return value data type. Valid values: BOOLEAN, DOUBLE, VARCHAR, INTEGER, TINYINT, BIGINT, TIME, DATE, TIMESTAMP, DATETIME.
func_nameThe 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 | NULLExtended 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...XnThe 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 parameterQuery-level parameterRequiredDescription
XIHE_REMOTE_CALL_SERVER_ENDPOINTendpointYesThe internal service endpoint of Function Compute. For details, see Service endpoints.
XIHE_REMOTE_CALL_SERVER_AKConditionalThe 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_SKConditionalThe 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_ENABLEDcompressedNoSpecifies whether to compress the data payload in GZIP format before sending it to Function Compute. Default: true.
XIHE_REMOTE_CALL_MAX_BATCH_SIZEmax_batch_sizeNoThe 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.
Important

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.

Data sending and returning formats

Input format

AnalyticDB for MySQL sends data to Function Compute in the following JSON structure:

{
  "rowCount": 3,
  "compressed": true,
  "data": [
    [1, "a", "2023-08-22 11:30:00"],
    [2, "b", "2023-08-22 12:30:00"],
    [3, "c", null]
  ]
}
FieldDescription
rowCountThe number of rows in the batch.
compressedSpecifies whether the transmitted data is compressed. Valid values: true and false.
dataThe transmitted data. This field consists of multiple JSON arrays. Each JSON array represents a row of data.

Output format

Function Compute must return results in the following JSON structure:

{
  "success": true,
  "message": "",
  "result": []
}
FieldDescription
successIndicates whether the request is successful. Valid values: true and false.
messageEmpty on success. Contains the error message on failure.
resultThe result computed in Function Compute.

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&1

Disable 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&1

Call 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&1

Troubleshooting

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.