risingwavelabs/risingwave · error
unsupported scheme in UDF link
Error message
unsupported scheme in UDF link: {} What it means
External UDF links must be `http://` or `https://` URLs; `https` enables TLS. If the URL parses but uses a different scheme and contains an explicit `://`, `parse_udf_link` rejects it, since other schemes (grpc, tcp, file) are not supported.
Solutions
- Change the link to http:// or https:// with the UDF service's host and port (defaults 80/443)
- If the service speaks gRPC, front it with an http-compatible endpoint as expected by the arrow-udf-wasm/remote protocol
- Remove the scheme to let RisingWave default to http://host:port
Example fix
-- before USING LINK 'grpc://127.0.0.1:50051' -- after USING LINK 'http://127.0.0.1:50051'
Defensive patterns
Strategy: validation
Validate before calling
const u = new URL(link); if (!['http:','https:'].includes(u.protocol)) throw new Error(`unsupported scheme in UDF link: ${u.protocol}`); Try / catch
try { await rw.query(`CREATE FUNCTION ... USING LINK '${link}'`) } catch (e) { if (String(e).includes('unsupported scheme in UDF link')) { /* rewrite link to http(s) and retry */ } else throw e; } Prevention
- Always use http:// or https:// in USING LINK
- Never copy grpc:// or file:// links
- Omit the scheme to default to http
When it happens
Trigger: `CREATE FUNCTION ... USING LINK 'grpc://host:port'` (or any non-http scheme with `://`) — any caller of `parse_udf_link` such as `connect_tonic`.
Common situations: Copying a link from a gRPC-based UDF example ('grpc://'), using 'unix://' sockets, or file:// links; older configs from before the http/https-only policy.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- Can't get fe host from url
- credentials_url must be a valid URL (s3://, file://) or an…
- {err}
- Failed to parse ldap url
- failed to parse meta address
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/dacdb8826d40e284.
Report an issue: GitHub.
Appendix: source
Thrown at src/expr/impl/src/udf/external.rs:236
// ginepro sets the TLS domain name (SNI) to `host` rather than the resolved IPs.
builder = builder.with_tls(ClientTlsConfig::new().with_native_roots());
}
let channel = builder
.channel()
.await
.with_context(|| format!("failed to create LoadBalancedChannel, address: {host}:{port}"))?;
Ok(channel.into())
}
/// Parse a UDF link into `(tls, host, port)`.
///
/// The link is an `http://` / `https://` URL, with `http://` as the default scheme when omitted.
/// `https` enables TLS; an omitted port defaults to 80 / 443.
fn parse_udf_link(link: &str) -> Result<(bool, String, u16)> {
let url = match url::Url::parse(link) {
Ok(url) if matches!(url.scheme(), "http" | "https") => url,
Ok(url) if link.contains("://") => {
bail!("unsupported scheme in UDF link: {}", url.scheme())
}
// no scheme (a plain `host:port` parses as scheme `host`): default to `http://`
_ => url::Url::parse(&format!("http://{link}"))
.with_context(|| format!("failed to parse UDF link: {link}"))?,
};
let host = url.host_str().expect("http(s) URL always has a host");
let port = url
.port_or_known_default()
.expect("http(s) scheme always has a default port");
Ok((url.scheme() == "https", host.to_owned(), port))
}
impl ExternalFunction {
/// Call a function, retry up to 5 times / 3s if connection is broken.
async fn call_with_retry(
&self,
input: &RecordBatch,
) -> Result<RecordBatch, arrow_udf_runtime::remote::Error> {View on GitHub (pinned to 6469eb736d)