risingwave_expr/sig/udf.rs
1// Copyright 2024 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! UDF implementation interface.
16//!
17//! To support a new language or runtime for UDF, implement the interface in this module.
18//!
19//! See expr/impl/src/udf for the implementations.
20
21use anyhow::{Context, Result, bail};
22use educe::Educe;
23use enum_as_inner::EnumAsInner;
24use futures::stream::BoxStream;
25use risingwave_common::array::arrow::arrow_array_udf::{ArrayRef, BooleanArray, RecordBatch};
26use risingwave_common::types::DataType;
27
28/// The global registry of UDF implementations.
29///
30/// To register a new UDF implementation:
31///
32/// ```ignore
33/// #[linkme::distributed_slice(UDF_IMPLS)]
34/// static MY_UDF_LANGUAGE: UdfImplDescriptor = UdfImplDescriptor {...};
35/// ```
36#[linkme::distributed_slice]
37pub static UDF_IMPLS: [UdfImplDescriptor];
38
39/// Find a UDF implementation by language.
40pub fn find_udf_impl(
41 language: &str,
42 runtime: Option<&str>,
43 link: Option<&str>,
44) -> Result<&'static UdfImplDescriptor> {
45 let mut impls = UDF_IMPLS
46 .iter()
47 .filter(|desc| (desc.match_fn)(language, runtime, link));
48 let impl_ = impls.next().context(
49 "language not found.\nHINT: UDF feature flag may not be enabled during compilation",
50 )?;
51 if impls.next().is_some() {
52 bail!("multiple UDF implementations found for language: {language}");
53 }
54 Ok(impl_)
55}
56
57/// UDF implementation descriptor.
58///
59/// Every UDF implementation describes how it is selected, created, and built, together with the
60/// capabilities supported by its execution path.
61pub struct UdfImplDescriptor {
62 /// Returns if a function matches the implementation.
63 ///
64 /// This function is used to determine which implementation to use for a UDF.
65 pub match_fn: fn(language: &str, runtime: Option<&str>, link: Option<&str>) -> bool,
66
67 /// Creates a function from options.
68 ///
69 /// This function will be called when `create function` statement is executed on the frontend.
70 pub create_fn: fn(opts: CreateOptions<'_>) -> Result<CreateFunctionOutput>,
71
72 /// Builds UDF runtime from verified options.
73 ///
74 /// This function will be called before the UDF is executed on the backend.
75 pub build_fn: fn(opts: BuildOptions<'_>) -> Result<Box<dyn UdfImpl>>,
76
77 /// Returns whether this implementation and function kind honor
78 /// `always_retry_on_network_error` during execution.
79 pub supports_always_retry_on_network_error: fn(kind: UdfKind) -> bool,
80}
81
82/// Options for creating a function.
83///
84/// These information are parsed from `CREATE FUNCTION` statement.
85/// Implementations should verify the options and return a `CreateFunctionOutput` in `create_fn`.
86pub struct CreateOptions<'a> {
87 pub kind: UdfKind,
88 /// The function name registered in RisingWave.
89 pub name: &'a str,
90 pub arg_names: &'a [String],
91 pub arg_types: &'a [DataType],
92 pub return_type: &'a DataType,
93 /// The function name on the remote side / in the source code, currently only used for external UDF.
94 pub as_: Option<&'a str>,
95 pub using_link: Option<&'a str>,
96 pub using_base64_decoded: Option<&'a [u8]>,
97}
98
99/// Output of creating a function.
100pub struct CreateFunctionOutput {
101 /// The name for identifying the function in the UDF runtime.
102 pub name_in_runtime: String,
103 pub body: Option<String>,
104 pub compressed_binary: Option<Vec<u8>>,
105}
106
107/// Options for building a UDF runtime.
108#[derive(Educe)]
109#[educe(Debug)]
110pub struct BuildOptions<'a> {
111 pub kind: UdfKind,
112 pub body: Option<&'a str>,
113 #[educe(Debug(ignore))]
114 pub compressed_binary: Option<&'a [u8]>,
115 pub link: Option<&'a str>,
116 pub name_in_runtime: &'a str,
117 pub arg_names: &'a [String],
118 pub arg_types: &'a [DataType],
119 pub return_type: &'a DataType,
120 pub always_retry_on_network_error: bool,
121 pub language: &'a str,
122 pub is_async: Option<bool>,
123 pub is_batched: Option<bool>,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumAsInner)]
127pub enum UdfKind {
128 Scalar,
129 Table,
130 Aggregate,
131}
132
133/// UDF implementation.
134#[async_trait::async_trait]
135pub trait UdfImpl: std::fmt::Debug + Send + Sync {
136 /// Call the scalar function.
137 async fn call(&self, input: &RecordBatch) -> Result<RecordBatch>;
138
139 /// Call the table function.
140 async fn call_table_function<'a>(
141 &'a self,
142 input: &'a RecordBatch,
143 ) -> Result<BoxStream<'a, Result<RecordBatch>>>;
144
145 /// For aggregate function, create the initial state.
146 async fn call_agg_create_state(&self) -> Result<ArrayRef> {
147 bail!("aggregate function is not supported");
148 }
149
150 /// For aggregate function, accumulate or retract the state.
151 async fn call_agg_accumulate_or_retract(
152 &self,
153 _state: &ArrayRef,
154 _ops: &BooleanArray,
155 _input: &RecordBatch,
156 ) -> Result<ArrayRef> {
157 bail!("aggregate function is not supported");
158 }
159
160 /// For aggregate function, get aggregate result from the state.
161 async fn call_agg_finish(&self, _state: &ArrayRef) -> Result<ArrayRef> {
162 bail!("aggregate function is not supported");
163 }
164
165 /// Whether the UDF talks in legacy mode.
166 ///
167 /// If true, decimal and jsonb types are mapped to Arrow `LargeBinary` and `LargeUtf8` types.
168 /// Otherwise, they are mapped to Arrow extension types.
169 /// See <https://github.com/risingwavelabs/arrow-udf/tree/main#extension-types>.
170 fn is_legacy(&self) -> bool {
171 false
172 }
173
174 /// Return the memory size consumed by UDF runtime in bytes.
175 ///
176 /// If not available, return 0.
177 fn memory_usage(&self) -> usize {
178 0
179 }
180}