CREATE EXTERNAL FUNCTION

On this page

The CREATE EXTERNAL FUNCTION command creates an external function, which is a function that calls code that is executed outside of a SingleStore database.

The CREATE EXTERNAL FUNCTION statement does not specify the function body.

This topic uses the following terms:

  • User-defined function (UDF): A function that returns a scalar value.

  • Table-valued function (TVF): A function that returns a TABLE value (a rowset).

An external function may be either a UDF or TVF.

Syntax

CREATE [OR REPLACE] EXTERNAL FUNCTION [<database_name>.]function_name ([<parameter_list>])
RETURNS <return_type>
[DEFINER = CURRENT_USER]
AS [COLLOCATED | REMOTE] SERVICE "<service_endpoint>"
FORMAT <data_format> [USING DELIMITED BATCHES] [LINK [database_name.]<connection_link_name>]
[PROTOCOL { V1 | V2 }]
parameter_list:
<variable_name> <data_type> [<data_type_modifier> [...n]] [, ...]
data_type_modifier:
DEFAULT <default_value> | NOT NULL | NULL | COLLATE collation_name

Return Type

  • return_type: A scalar value or a TABLE value. If a TABLE value, includes a list of <column name> <data_type> values such as TABLE(a TEXT, b INT).

Arguments

  • function_name: The name of the function. Function names must be unique within a database, and cannot be duplicates of names for other tables, views, UDFs, TVFs, user-defined aggregate functions (UDAFs), or stored procedures.

    You can also specify <database_name> and <function_name> together by replacing <function_name> with <database_name>.<function_name> instead of defaulting to the current context database. For example, you can write the following:

    CREATE EXTERNAL FUNCTION db.some_func(a int) ...
  • parameter_list: A list of function parameters in the <variable_name> <data_type> [<data_type_modifier>] format. Refer to Supported Data Types for the data types accepted by the function parameters.

  • data_format: One of the following: JSON or ROWDAT_1. For more information, refer to Data Formats.

  • connection_link_name: The name of a connection link, which is a secure link that stores connection details (credentials and configurations) for an HTTP connection. Refer to Configuring and Using Connection Links for more details.

  • DEFINER = current_user: (Optional) When specified, a special flag is stored in the metadata table which indicates if a different user is required to run the function.

  • AS [COLLOCATED | REMOTE] SERVICE: Specifies where and how the external service runs. The default service type is REMOTE. Refer to AS REMOTE SERVICE and AS COLLOCATED SERVICE for more information.

Remarks

Note

SingleStore does not start, stop, or otherwise maintain the service that hosts the external function.

  • A call to an external UDF can be made anywhere in a SQL statement where an expression is allowed. For example:

    • SELECT example_external_udf(100,'foo');
    • SELECT * FROM t
      WHERE example_external_udf(100,'foo') = 'bar';
    • SELECT a, COUNT(*) FROM t
      GROUP BY a
      HAVING a > example_external_udf(100,'foo');
  • A call to an external TVF can be made anywhere in a SQL statement where a table reference is allowed, including joining to a table. For example:

    • SELECT * FROM example_external_tvf(100,'foo'); 
    • SELECT * FROM t, example_external_tvf(100,'foo') AS e
      WHERE t.id = e.id;
  • SingleStore does not send keep-alive probes or enable connection pooling for remote external functions.

Enable External Functions

External functions must be enabled before they can be used. To enable external functions, set the enable_external_functions engine variable to either of the following as required:

Value

Description

ON

Enables the use of all external functions.

ALLOWLIST

Enables the use of all the external functions in a specified list. When enable_external_functions is set to ALLOWLIST, you must configure the external_functions_allowlist engine variable and specify the names of external functions to enable (allow). You can use wildcards in the names.

For example, to enable the use of any external function located at http://host1.example.com and the functions named lowercase and uppercase located at http://host2.example.com, set the engine variable external_functions_allowlist to:

"{'endpoints' : ['http://host1.example.com/*', 'http://host2.example.com/uppercase','http://host2.example.com/lowercase']}"

By default, enable_external_functions is set to OFF.

Handshake Protocol

The CREATE EXTERNAL FUNCTION statement supports an optional protocol clause: PROTOCOL {V1 | V2}.

PROTOCOL V1 is the default and remains backward compatible with existing external services. PROTOCOL V2 adds the input parameter type list to the handshake. Use PROTOCOL V2 when an external service supports overloaded functions and needs to distinguish between function signatures. 

If PROTOCOL is omitted, SingleStore uses PROTOCOL V1. Existing external functions therefore continue to use the legacy handshake unless they are recreated or replaced with PROTOCOL V2.

The USING DELIMITED BATCHES clause is independent of the handshake version. When it is used, the external service must implement the final zero-size batch and the response behavior.

Protocol

Behavior

Use Case

V1

Sends the original handshake metadata without input parameter types.

Use with existing services that do not understand V2 metadata.

V2

Extends V1 with the ordered input parameter type list.

Use when the external service must distinguish overloaded functions by signature.

The EXTERNAL_PROTOCOL_VERSION column in the ROUTINES information schema view shows the protocol version of an existing external function. Alternatively, run the SHOW CREATE FUNCTION command to view the protocol version. A V2 definition includes PROTOCOL V2. Whereas, a V1 definition may omit the clause because PROTOCOL V1 is the default.

Protocol V2 Metadata

For an external function defined with PROTOCOL V2, SingleStore encodes the parameter type list as an ordered, semicolon-separated list. Each item corresponds to one input parameter in the SQL function definition. The type list uses the canonical SQL type names and omits length, collation, and other type modifiers. For example, a function declared with BIGINT UNSIGNED and VARCHAR(20) COLLATE utf8mb4_bin sends the following type list: BIGINT;VARCHAR.

For a zero-argument external function, SingleStore omits the parameter type metadata because there are no input types to report.

PROTOCOL V2 sends input parameter type metadata for every external function, including non-overloaded functions.

Remote and Managed Services: HTTP Handshake

Remote and managed services use a HTTP POST request. The request body contains the function input data, and headers identify the function, data format, protocol, and, for V2, the input parameter types.

Protocol V1 HTTP Request Headers

The default, PROTOCOL V1, request contains the following metadata headers:

Accept: application/<type>
Content-Type: application/<type>
Content-Length: <length>
User-Agent: SingleStore/<server-version>
s2-ef-name: <functio-name>
s2-ef-version: 1.0

where,

  • <type> is json for JSON format and octet-stream for ROWDAT_1 format.

  • Content-Length is the length of the request body. 

  • s2-ef-name and s2-ef-version are custom HTTP headers that specify the external function name and handshake version, respectively.

Connection links can add user-configured headers, but they cannot override reserved headers such as s2-ef-*, Accept, Content-Type, Content-Length, or User-Agent.

Protocol V2 HTTP Request Headers

PROTOCOL V2 extends the PROTOCOL V1 headers to include the input parameter types. For example:

Accept: application/<type>
Content-Type: application/<type>
Content-Length: <length>
User-Agent: SingleStore/<server-version>
s2-ef-name: <function_name>
s2-ef-version: 2.0
s2-ef-input-param-types: BIGINT;VARCHAR;FLOAT

Note that s2-ef-version is 2.0 for PROTOCOL V2.

For a zero-argument external function, SingleStore omits the parameter type metadata because there are no input types to report.

The current implementation validates the constructed Protocol V2 parameter-type header at create time for remote and managed services. The maximum header buffer is 1100 bytes; a serialized header at or above this limit is rejected, and the error advises reducing the number of input parameters.

Collocated Services: UNIX-Domain Socket Handshake

Collocated services communicate over a UNIX-domain socket. Input and output data are transferred through shared-memory files, while handshake and batch-control metadata are transferred through the socket. The engine sends two shared-memory file descriptors using ancillary SCM_RIGHTS data.

Protocol V1 Wire Format

Protocol V1 sends the following fields during connection initialization:

[8 bytes] protocol_version       // uint64, value 1
[8 bytes] function_name_length   // uint64, N
[N bytes] function_name          // UTF-8 function name
[ancillary data] input_fd, output_fd

The two file descriptors identify the input and output shared-memory files.

After the handshake, the engine sends the input batch length as a 64-bit integer. The service returns two 64-bit integers: status code and output length. Status 200 indicates success; another status indicates an error.

Protocol V2 Wire Format

Protocol V2 preserves the V1 fields and appends the parameter-type metadata after the function name and file descriptors:

[8 bytes] protocol_version       // uint64, value 2
[8 bytes] function_name_length   // uint64, N
[N bytes] function_name          // UTF-8 function name
[ancillary data] input_fd, output_fd
[8 bytes] parameter_types_length // uint64, M
[M bytes] parameter_types_buffer // UTF-8: TYPE1;TYPE2;...

For example:

protocol_version:       2
function_name_length:   11
function_name:          PrettyPrint
input_fd, output_fd:    SCM_RIGHTS ancillary data
parameter_types_length: 14
parameter_types_buffer: BIGINT;VARCHAR

The additional parameter-type fields are sent during connection initialization and are not repeated for every batch on the connection.

For a zero-argument external function, SingleStore sends parameter_types_length: 0 and omits the parameter_types_buffer because there are no input types to report.

Overloaded External Functions

External functions support overloading. You can define multiple external UDFs or TVFs with the same name when they have different input parameter lists. Refer to Overloaded Functions and Stored Procedures for information on enabling overloaded functions, supported data types in function signature of overloaded functions, and other requirements.

When a query calls an overloaded external function, the database resolves the function by using the function name and the supplied input parameters, following the same overloading framework used for regular UDFs and stored procedures.

To create an overloaded external function, specify PROTOCOL V2 in the function definition. For example:

CREATE OR REPLACE EXTERNAL FUNCTION format_value(value BIGINT)
RETURNS TEXT
AS REMOTE SERVICE 'https://example.com/functions/format-value'
FORMAT JSON PROTOCOL V2;
CREATE OR REPLACE EXTERNAL FUNCTION format_value(value VARCHAR(16))
RETURNS TEXT
AS REMOTE SERVICE 'https://example.com/functions/format-value'
FORMAT JSON PROTOCOL V2;

When the external function uses PROTOCOL V2, the handshake includes the input parameter types. This enables the external service to distinguish between different function signatures and identify which overloaded implementation the database is calling. External function overloading follows the same resolution model as overloaded regular UDFs and stored procedures. Refer to Overloaded Function and Procedure Resolution for more information.

Batching of Data Sent from SingleStore to an External Function

A batch contains multiple rows of data (as defined in the external function declaration). A row_id is added to each row of data in the batch.

Batching of data sent from SingleStore to an external TVF

SingleStore always sends data in batches to an external TVF.

Batching of data sent from SingleStore to an external UDF

External UDFs are rewritten by SingleStore to external TVFs to allow external UDFs to be pushed down to a table scan to support batch processing of inputs.

However, not all external UDFs can be rewritten as TVFs. Only UDFs contained in the projection list (the columns that are selected by a query) or the WHERE clause of a SELECT can be rewritten as external TVFs. Furthermore, these external UDFs must not receive aggregate functions, window functions, or nested SELECT statements as input.

The batch_external_functions engine variable controls the rewrites of external UDFs to external TVFs for batching. The possible values are as follows:

  • ALWAYS: Any query, which contains an external UDF that SingleStore cannot convert to an external TVF, will not be executed. External UDFs used in all other types of queries will run and will be rewritten to TVFs.

  • AUTO: SingleStore rewrites all external UDFs that it can to external TVFs for batching. Any external UDFs that cannot be rewritten will be called one row at a time.

  • NEVER: All external UDFs will be called without batching (i.e., one row at a time).

Send Delimited Batch Sets

Specify the USING DELIMITED BATCHES clause to keep the connection open after all the batches have been processed by SingleStore and accept a final batch from the external function. For example:

CREATE EXTERNAL FUNCTION extFunc(a TEXT)
RETURNS TEXT AS REMOTE SERVICE 'http://<svchost>/<endpoint>'
FORMAT ROWDAT_1 USING DELIMITED BATCHES;

By default, once SingleStore processes all the rows in the batch, it sends a final batch of zero size and closes the connection to the external function. However, the external function may sometimes send additional rows after all the rows have been processed. Enabling delimited batch sets allows the external function to acknowledge that all the rows have been received and send additional rows within the last response if required.

Implement Delimited Batch Sets in External Functions

When USING DELIMITED BATCHES is specified in CREATE EXTERNAL FUNCTION statements, SingleStore appends the batchdelim=true parameter to the function name in the URI, which indicates that delimited batch sets are enabled for this function. External functions can check this suffix to confirm if the function supports delimited batches.

If delimited batch sets are enabled for an external function, SingleStore waits for a response after sending the final zero-size batch. Hence, the external function must send a response after receiving the last batch of zero size as follows:

  • If there are no additional rows to send, the external function must respond with a HTTP status message and a response size of 0.

  • If additional rows exist, the row_id of the rows sent in the response must match the row_id of the original rows in the target SingleStore table. SingleStore matches the rows in the last batch with the corresponding rows in the table using the row_id.

When USING DELIMITED BATCHES is specified, SingleStore does not send keep-alive probes or create a connection pool across queries; it only changes the final empty-batch acknowledgment behavior within a query.

Benefits of Batching

  • Batching reduces network and CPU utilization.

  • Batching does increase memory utilization. However, the benefits of the decrease in network and CPU utilization far outweigh the increase in memory utilization.

  • A global variable which assists with batching is external_functions_batch_size. It controls the batch size, in rows, for calls to the external process that services external functions. It has a default size of 512 rows. Most applications should simply use the default, but if the overhead of calling the external process becomes significant, you may wish to increase the batch size.

Tune External Function Workloads

While the default batch size is sufficient for most use cases, if an external function spends significant time waiting for the next batch or there is significant overhead in calling the external process, increase the batch size using the external_functions_batch_size engine variable. A larger batch can reduce the relative overhead of serialization, network transfer, and external process invocation. But, larger batches may also increase the memory usage.

Verify Batching

Before increasing the batch size, confirm that the query is using batched execution:

  • SingleStore always sends data to an external TVF in batches.

  • An external UDF can be rewritten to an external TVF only for supported query shapes. With batch_external_functions = AUTO, SingleStore rewrites external UDFs when possible. An external UDF that cannot be rewritten is called one row at a time.

  • With batch_external_functions = NEVER, external UDFs are called without batching.

If the external function is called one row at a time, increasing the batch size does not improve performance. Review the query shape and batch_external_functions.

Verify Flexible Parallelism

For large analytic workloads, flexible parallelism allows multiple cores on a leaf node to process portions of a database partition in parallel. Check the configuration and query plan before changing query parallelism. For example:

SHOW VARIABLES LIKE 'expected_leaf_core_count';
SELECT @@query_parallelism_per_leaf_core;
SELECT @@sub_to_physical_partition_ratio;
SELECT database_name, num_partitions, num_sub_partitions
FROM information_schema.distributed_databases
WHERE database_name = '<database_name>';

A query_parallelism_per_leaf_core value of 0 disables flexible parallelism. Values greater than 0 enable it when the query and database layout support it. The default value of 1 allows a parallel operation to use all the available CPU cores on each leaf node, but the actual parallelism depends on the query shape.

Use EXPLAIN or PROFILE to verify the result. The parallelism_level in the EXPLAIN output indicates whether an operator uses partition, sub_partition, or segment parallelism. Queries on rowstore tables and query shapes that do not support flexible parallelism may not use it even when query_parallelism_per_leaf_core is non-zero.

For information on supported flexible-parallelism modes and database requirements, refer to Flexible Parallelism.

Tune Batch Size

SingleStore recommends performing the following actions, in order, to find the optimal batch size:

  1. Establish a baseline with the current batch size and record the query time, external service processing time, CPU and memory usage, and the time spent waiting for input.

  2. Test a batch with a larger size, for example 4096 rows, to evaluate its impact on performance and resource usage. Increase the batch size using external_functions_batch_size.

  3. If the workload benefits from the large batch size, test with a larger batch size. Keep the value that provides the best performance. Note: Increasing the batch size beyond an optimal value can have diminishing returns. Avoid increasing the batch size indefinitely.

  4. Reevaluate the performance using representative data volumes and concurrency levels.

A batch size that performs well for one workload may not be optimal for another.

Supported Data Types

External functions support the following data types:

Category

Data Type

Integer

TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT, BOOL

Floating-point

FLOAT, DOUBLE

String

VARCHAR, TEXT, CHAR, BLOB, LONGTEXT, MEDIUMTEXT, TINYTEXT, LONGBLOB, MEDIUMBLOB, TINYBLOB, BINARY, VARBINARY

JSON

JSON

Unsupported Data Types

The following data types are not supported:

Category

Data Type

Number

DECIMAL, NUMERIC

UNSIGNED integer types (e.g., INT UNSIGNED, BIGINT UNSIGNED)

Time and Date

DATE, TIME, DATETIME, TIMESTAMP, YEAR

Other Types

GEOMETRY, BIT, SET, ENUM

To pass an unsupported data type to an external function, explicitly cast it to a supported data type in the query. For example:

SELECT func(CAST(val_date AS BIGINT), CAST(val_decimal AS DOUBLE)) ...

Unsupported types are rejected and the CREATE EXTERNAL FUNCTION statement returns an error.

Data Formats

External functions support two data formats, JSON and ROWDAT_1. Specify the format to use in a CREATE [OR REPLACE] EXTERNAL FUNCTION statement. The selected format specifies how the input row(s) sent to the external function and output row(s) returned by the external function are serialized.

JSON

If batching is used, the data format is serialized as a JSON array of arrays as follows:

{"data":
[[<row_id>, <argument 1>, <argument 2>, …],
[<row_id>, <argument 1>, <argument 2>, …],
...
]}

If batching is not used, the data format is serialized as a JSON array containing one row.

{"data":
[[<row_id>, <argument 1>, <argument 2>, …]]
}

Each JSON sub-array contains the following fields, in order:

  • row_id: A row ID, which is an integer greater than or equal to 0. The row_id is generated for the rows sent as input to the external function. The output rows are listed in ascending order. Duplicate row_id values are allowed.

  • argument n: Refer to Supported Data Types for the data types accepted by the JSON serialization format.

In the JSON format, SQL NULL and JSON null are both represented as null; an external function cannot distinguish between them. If this distinction matters (e.g., for JSON-typed parameters), use the ROWDAT_1 format. In ROWDAT_1, the NULL indicator is transmitted separately from the value bytes.

Note

Because a single row or multiple rows (a batch) may be sent as input to an external function, the function must be written to handle both the cases.

ROWDAT_1

ROWDAT_1 is a binary format. It is used for serializing data that is the input to and the output from an external function. The data is in rowstore format which contains one row, or multiple rows if batching is used.

Each row contains the following fields, in order:

  • A row_id, which is an integer greater than or equal to 0. For ROWDAT_1 output, row_id values must be strictly increasing. Each row_id must be greater than the previous row_id.

  • The remaining fields contain the arguments to the external function. The ROWDAT_1 serialization format encodes the respective values as follows:

    • Integer: An 8-byte signed integer (64-bit).

    • Double: An 8-byte IEEE 754 double (64-bit).

    • String (length-encoded variable-length string): An 8-byte little-endian unsigned integer that specifies the length of the string in bytes, followed by the corresponding number of bytes containing the string data.

    • JSON: Encoded using the same format as strings, with an 8-byte length prefix followed by the JSON text represented as UTF-8 bytes.

The serialization format is little-endian. A single byte denoting whether a value is NULL precedes each value, except the row_id which cannot be NULL, where:

  • 1: NULL, indicates that no bytes follow for this field.

  • 0: Indicates the following value is not NULL.

Note

Because a single row or multiple rows (a batch) may be sent as input to an external function, the function must be written to handle both the cases.

AS REMOTE SERVICE

Use AS REMOTE SERVICE '<service_endpoint>' to create an external function that is accessible through a remotely hosted HTTP service, where service_endpoint specifies the URL of the remote service endpoint. SingleStore sends the function input as an HTTP request and expects an HTTP response.

Note

At high call rates, workloads that issue one HTTP request per SQL statement for remote external functions can accumulate TCP TIME_WAIT sockets.

Following are the specifics of the HTTP request and response format.

HTTP Request Method

  • POST: A standard HTTP POST request is used.

Standard HTTP Request Headers

  • accept/content-type:

    • application/json, or

    • application/octet-stream

  • user-agent: SingleStore/<SingleStore_version>

Custom HTTP Request Headers

  • s2-ef-version: Handshake protocol version.

  • s2-ef-name: Name of the external function.

HTTP Response

When a remote function runs successfully, it must return a HTTP response with status code 200. A HTTP response with status code other than 200 is treated as an error, and the response body contains the error message.

Refer to Handshake Protocol for information on handshake protocol and wire format. Refer to Data Formats for information on supported data formats.

AS COLLOCATED SERVICE

Use the AS COLLOCATED SERVICE '<socket_path>' option when the external service runs locally on each SingleStore node that may run the function, where socket_path specifies the filesystem path to a Unix domain socket on which the collocated service listens. SingleStore connects to the Unix domain socket, while input and output data are sent via shared memory files.

The engine creates two in-memory files via memfd_create, both sealed with F_SEAL_SHRINK, which allows the files to only grow in size but not shrink and thus is safe to mmap. The input/output files use sendmsg to transmit to the Unix domain socket as ancillary data. Use the external_functions_service_buffer_mb engine variable to configure the maximum size of the memory-mapped region used to communicate between the SingleStore engine and collocated services.

Refer to Handshake Protocol for information on handshake protocol and wire format.

For each input batch, the input and output are written to the corresponding shared file in either the ROWDAT_1 or JSON format. Each new batch overwrites the data from the previous batch. Refer to Data Formats for information on supported data formats.

The status code conforms to the HTTP standard. A status code of 200 indicates a successful request. Any status code other than 200 indicates that an error occurred.

Before calling an external function, start the collocated service and configure it to listen on the Unix domain socket specified in the function definition. Run one listener process on each SingleStore node that can run the function, including aggregator and leaf nodes. Ensure that the memsqld operating-system user has permission to connect to the Unix domain socket.

Refer to Example: SingleStore Collocated Service Using Binary Protocol for an example.

SingleStore Transaction Semantics Are Not Enforced Inside of External Functions

Warning

Any update to state made by an external function is not part of the calling SingleStore transaction. If the calling transaction rolls back, it does not roll back updates made by an external function.

For example, suppose an application runs a SQL query which calls an external function. The function runs and increments a variable v held in an external process by 1, from 100 to 101, and returns v. After the function completes, but before the transaction commits, the application running the transaction calls ROLLBACK. The state of the variable v in the external process remains at 101; it is not rolled back to 100.

An HTTP connection link can be used when creating an external function. Using a connection link is more secure than specifying credentials and other sensitive connection details directly in the command. Examples are provided at the end of this topic.

HTTP connections have the following limitations:

  • Only external functions can use them.

  • They cannot contain the following header keys: s2-ef-..., Accept, Connection, Content-Length, Content-Type, Upgrade, User-Agent.

The following example shows how to create an HTTP link, which you can use in a CREATE [OR REPLACE] EXTERNAL FUNCTION statement.

CREATE LINK HTTP_link AS HTTP
CREDENTIALS '{"headers":{"authorization":"Bearer <token>"}}'
CONFIG '{"headers":{"version":"2.3"}}';

The following example shows how to create an HTTP link, which you can use in a CREATE [OR REPLACE] EXTERNAL FUNCTION statement.

CREATE LINK HTTP_link AS HTTP
CREDENTIALS '{"headers":{"authorization":"Bearer <token>", "version":"2.3"}}';

To create an encrypted HTTP connection (encrypts the traffic between the database and the remote endpoint), set the following security-specific CONFIG/CREDENTIALS JSON:

{
  "security": {
    "security.protocol": "ssl",
    "ssl.key.location": "/path/to/key.pem",
    "ssl.key.password": "<password_for_private_key>",
    "ssl.certificate.location": "/path/to/cert.pem"
    "ssl.ca.location": "/path/to/ca.pem"
  }
}

Where:

  • security.protocol: must be "ssl". No other protocols are supported/implemented.

  • ssl.key.location: required. Specifies the path to the private key that will be used during SSL negotiation with a remote server.

  • ssl.key.password: optional. Used if ssl.key.location points to a password-protected private key.

  • ssl.certificate.location: required. Specifies the path to the certificate with the signature of the public part of the key during the SSL handshake.

  • ssl.ca.location: optional. If specified, the SingleStore engine will verify the peer’s certificate against the CA bundle loaded from the provided path.

You can omit the headers object in the JSON when configuring encrypted connections.

Example: Encrypt Traffic Between Database and Remote Endpoint

CREATE LINK link_1 AS HTTP
CREDENTIALS '{"security":{"ssl.key.location":"<path_to_private_key>/server-key.pem"}}'
CONFIG '{"security":{
"security.protocol":"ssl",
"ssl.certificate.location":"<path_to_pem>/server-cert.pem",
"ssl.ca.location":"/<path_to_ca_bundle>/ca-cert.pem"}}';
CREATE OR REPLACE EXTERNAL FUNCTION 'ef1' (input INT) RETURNS TEXT AS REMOTE SERVICE
'https://localhost:8123/func1' FORMAT JSON LINK link_1;
SELECT ef1(42);

The following example shows how to create a basic encrypted HTTP link using only the CA bundle path in the security settings in the CONFIG clause. You can create an HTTP link using only the CA bundle path if the authentication of the SingleStore engine is not required, and only encryption and remote certificate verification are needed. You do not need to specify "security.protocol":"ssl" in this case.

CREATE LINK link_2 AS HTTP
CREDENTIALS ''
CONFIG '{"security":{"ssl.ca.location":"<path_to_pe>/ca-cert.pem"}}';

Examples

Example: An External UDF

Suppose you are writing an external function that implements an elementary grammar checker. The checker scans one paragraph at a time. The checker corrects each paragraph by adding missing periods at the end of sentences and capitalizes the first letter of sentences (if the first letter is lowercase). Also, the checker optionally replaces digits, that are standalone words, in the sentence with the corresponding numbers spelled out. If no corrections to a paragraph are needed, the checker outputs the paragraph, unchanged.

Create the paragraphs table which stores the paragraphs to be checked and whether to check the digits in each paragraph.

CREATE TABLE paragraphs(paragraph TEXT, check_digits BOOL);

The table contains the following data.

+---------------------------------------------------------------+--------------+
| paragraph                                                     | check_digits |
+---------------------------------------------------------------+--------------+
| this paragraph has 1 sentence                                 |            1 |
| this paragraph has 2 sentences. this is the second sentence   |            0 |
| This paragraph has one sentence and doesn't need corrections. |            1 |
+---------------------------------------------------------------+--------------+

Create the definition for an external function that checks a paragraph for grammar mistakes. If 1 is sent as input to the check_digits parameter, then digits are included as part of the grammar check.

CREATE EXTERNAL FUNCTION check_paragraph(paragraph TEXT, check_digits BOOL)
RETURNS TEXT
AS REMOTE SERVICE '<endpoint_URL>'
FORMAT JSON;

Implement the body of the external function and store the implementation on the HTTP server. An example implementation using an AWS Lambda function written in Python follows. Note that the implementation of the helper functions convert_numbers_to_words, capitalize_sentences and add_missing_periods_to_sentences is not shown.

import json
def lambda_handler(event, context):
request = json.loads(event['body'])
response = []
for row_id, paragraph, check_digits in request["data"]:
if check_digits:
paragraph = convert_numbers_to_words(paragraph)
paragraph = capitalize_sentences(paragraph)
paragraph = add_missing_periods_to_sentences(paragraph)
response.append([row_id, paragraph])
return { 'statusCode': 200, 'body': json.dumps({"data": response}) }

Query the paragraphs table and call the external function as part of the query.

SELECT check_paragraph(paragraph, check_digits) as corrected_paragraph,
check_digits FROM paragraphs;

The external UDF is rewritten to an external TVF; the query calls the external function only once with the following input:

{"data":
    [[0, "this paragraph has 1 sentence", 1],
    [1, "this paragraph has 2 sentences. this is the second sentence", 0],
    [2, "This paragraph has one sentence and doesn't need corrections.", 1]]
}

After processing the input data, the external function returns the following data to the calling query. The data contains the corrected paragraphs. Paragraphs not needing any corrections are also included.

{"data":
    [[0, "This paragraph has one sentence."],
    [1, "This paragraph has 2 sentences. This is the second sentence."],
    [2, "This paragraph has one sentence and doesn't need corrections."]]
}

The query returns the following output.

+---------------------------------------------------------------+--------------+
| corrected_paragraph                                           | check_digits |
+---------------------------------------------------------------+--------------+
| This paragraph has one sentence.                              |            1 |
| This paragraph has 2 sentences. This is the second sentence.  |            0 |
| This paragraph has one sentence and doesn't need corrections. |            1 |
+---------------------------------------------------------------+--------------+

Example: External UDF using DEFINER

CREATE EXTERNAL FUNCTION ext_func()
RETURNS TEXT DEFINER ='root'@'%'
AS REMOTE SERVICE '...' FORMAT JSON;

To view a list of the functions and see the definer used:

SHOW FUNCTIONS;
+------------------------+------------------+----------+-------------+-----------------+------+
| Functions_in_func_proc | Function Type    | Definer  | Data Format | Runtime Type    | Link |
+------------------------+------------------+----------+-------------+-----------------+------+
| ext_func               | External User    | root@%   | JSON        | Remote Service  |      |
|                        | Defined Function |          |             |                 |      |
+------------------------+------------------+----------+-------------+-----------------+------+

Example: An External TVF

Suppose you want to read the contents of an external file and store each line in a row in a table.

Create the definition for the external function, which gets the contents of a file that is passed as input.

CREATE EXTERNAL FUNCTION get_file_lines(filename TEXT)
RETURNS TABLE(line TEXT)
AS REMOTE SERVICE '<endpoint_URL>'
FORMAT JSON;

Implement the body of the external function and store the implementation on the HTTP server. The implementation is not shown here.

Query the external function:

SELECT line FROM get_file_lines("simple_file.txt");

When the external function runs, it returns a JSON array of arrays, which contains the data in simple_file.txt:

{"data":
    [[0, "This is the first sentence."],
    [0, "This is the second sentence."],
    [0, "This is the third sentence."]]
}

When the query that calls the external function completes, the query returns the following output.

+------------------------------+
| line |
+------------------------------+
| This is the first sentence. |
| This is the second sentence. |
| This is the third sentence. |
+------------------------------+

Implementing High-Performance Parallel External Functions

Fast, large-batch processing can be accomplished, when in an on-premises configuration, by implementing high-performance parallel external functions. This is done by configuring every node in your SingleStore system to act as a parallel, multi-threaded application server.

By using this approach, rows are batched when sent to a UDF, cross-process calls are not done for every row, and data is not sent across the network. This allows for very fast per-row processing speed. Also, this scales the amount of CPU used for external function computation proportionally to the sizes of your cluster. This avoids potential CPU bottlenecks that could occur if all calls are directed to an external URL (depending on how your networking and the application service to run functions are designed). Other advantages are: any code can be utilized and legacy code and DLLs can be run without needing to recompile the DLLs.

Example: Using Remote Service

The default service type is REMOTE and the endpoint is expected to be the URL of a remote HTTP service. The following example uses an AWS endpoint as the remote service:

CREATE OR REPLACE EXTERNAL FUNCTION uppercase_string(string_col TEXT)
RETURNS TEXT
AS REMOTE SERVICE
'https://fnc.execute-api.us-east-1.amazonaws.com/prod/upcase_string'
FORMAT JSON;

Example: Using localhost as Remote Service

CREATE EXTERNAL FUNCTION ext_udf2(col_a INT, col_b INT)
RETURNS TABLE(col_1 TEXT, col_2 BOOL)
AS REMOTE SERVICE 'http://localhost:3305/cool_table_func'
FORMAT JSON;

Example: Using Collocated Service

CREATE EXTERNAL FUNCTION lowercase_string(string_col TEXT)
RETURNS TEXT
AS COLLOCATED SERVICE 'tmp/s2conn.sock'
FORMAT ROWDAT_1;

Stage Rows by Reference for Collocated TVFs

When an external table-valued function (TVF) is called, it keeps the input row’s columns available until the external service returns its response. By default, each input row is copied into an intermediate staging area.

Set external_functions_vpi_ref_staging to ON to allow eligible queries to skip this copy. The variable is OFF by default.

SET GLOBAL external_functions_vpi_ref_staging = ON;

Setting a global engine variable requires the SUPER privilege.

This variable participates in the plan cache, when in the default mode, such that queries are recompiled after the value changes. Plans compiled with the variable enabled and disabled can coexist in the plan cache.

A query uses staging by reference only when all of the following conditions are met:

  • external_functions_vpi_ref_staging is ON.

  • enable_external_functions is ON.

  • batch_external_functions is not set to NEVER.

  • The query joins exactly one local columnstore table to exactly one external TVF.

  • The join is an inner join.

  • The external function uses AS COLLOCATED SERVICE and FORMAT ROWDAT_1.

  • The external function does not use USING DELIMITED BATCHES.

Queries that do not meet these conditions use the default copy behavior.

Aligning the external function batch size with the columnstore scan batch size can improve performance when staging by reference. For best results, set external_functions_batch_size to 4096:

SET GLOBAL external_functions_batch_size = 4096;

Because external_functions_batch_size is a global setting, evaluate the effect on workloads that use the default copy behavior before changing it cluster-wide.

The benefit comes from reducing data copying. Queries that pass several wide TEXT or VARCHAR columns to the external TVF may see the maximum gains.

Queries that project only the function argument and its result may see little improvement because they already copy very little data. Highly selective filters may also reduce the benefit. Measure a representative query with the variable set to OFF and ON before enabling it for a production workload.

The external_function_staged_by_ref variable is available in JSON EXPLAIN. Use PROFILE and SHOW PROFILE JSON to inspect the runtime batch and row counters.

SET GLOBAL external_functions_vpi_ref_staging = ON;
PROFILE SELECT COUNT(*), SUM(LENGTH(t.description)), e.result
FROM products AS t JOIN classify_tvf(t.name) AS e;
SHOW PROFILE JSON;

The five batching counters are reported on the external function join operator in text and JSON PROFILE output. The external_function_staged_by_ref variable is reported on the external TVF node in the join’s right-input subtree.

Field

Description

external_function_staged_by_ref

Indicates whether the query uses staging by reference. This field appears in JSON output only.

external_function_batches

The number of batches sent to the external service.

external_function_rows

The number of input rows sent to the external service.

external_function_max_batch_rows

The number of rows in the largest batch.

external_function_avg_batch_rows

The average number of rows per batch.

external_function_block_limited_batches

The number of batches sent before reaching external_functions_batch_size.

Compare external_function_avg_batch_rows with external_functions_batch_size to evaluate batch utilization. Compare external_function_block_limited_batches with external_function_batches to assess how effectively batches use the configured batch size.

Last modified:

Was this article helpful?

Verification instructions

Note: You must install cosign to verify the authenticity of the SingleStore file.

Use the following steps to verify the authenticity of singlestoredb-server, singlestoredb-toolbox, singlestoredb-studio, and singlestore-client SingleStore files that have been downloaded.

You may perform the following steps on any computer that can run cosign, such as the main deployment host of the cluster.

  1. (Optional) Run the following command to view the associated signature files.

    curl undefined
  2. Download the signature file from the SingleStore release server.

    • Option 1: Click the Download Signature button next to the SingleStore file.

    • Option 2: Copy and paste the following URL into the address bar of your browser and save the signature file.

    • Option 3: Run the following command to download the signature file.

      curl -O undefined
  3. After the signature file has been downloaded, run the following command to verify the authenticity of the SingleStore file.

    echo -n undefined |
    cosign verify-blob --certificate-oidc-issuer https://oidc.eks.us-east-1.amazonaws.com/id/CCDCDBA1379A5596AB5B2E46DCA385BC \
    --certificate-identity https://kubernetes.io/namespaces/freya-production/serviceaccounts/job-worker \
    --bundle undefined \
    --new-bundle-format -
    Verified OK

Try Out This Notebook to See What’s Possible in SingleStore

Get access to other groundbreaking datasets and engage with our community for expert advice.