{"record":{"id":"bf8b7a1451e58423","repo":"databendlabs/databend","slug":"path-in-url-must-end-with-usage-got","errorCode":null,"errorMessage":"path in URL must end with '/' {usage}. Got '{}'.","messagePattern":"path in URL must end with '/' (.+?)\\. Got '(.+?)'\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/query/sql/src/planner/binder/location.rs","lineNumber":504,"sourceCode":"        token: l.connection.get(\"token\").cloned().unwrap_or_default(),\n        network_config: None,\n    });\n\n    l.connection\n        .check()\n        .map_err(|err| Error::new(ErrorKind::InvalidInput, err.to_string()))?;\n\n    Ok(sp)\n}\n\npub async fn parse_storage_params_from_uri(\n    l: &mut UriLocation,\n    usage: &str,\n) -> Result<StorageParams> {\n    if !l.path.ends_with('/') {\n        return Err(Error::new(\n            ErrorKind::InvalidInput,\n            anyhow!(\"path in URL must end with '/' {usage}. Got '{}'.\", l.path),\n        ));\n    }\n    Ok(parse_uri_location(l).await?.0)\n}\n\nstruct ParsedUriLocation {\n    root: String,\n    path: String,\n    protocol: Scheme,\n}\n\nfn parse_uri_location_parts(l: &UriLocation) -> Result<ParsedUriLocation> {\n    // Path ends with `/` means it's a directory, otherwise it's a file.\n    // If the path is a directory, we will use this path as root.\n    // If the path is a file, we will use `/` as root (which is the default value)\n    let (root, path) = if l.path.ends_with('/') {\n        (l.path.clone(), \"/\".to_string())\n    } else {","sourceCodeStart":486,"sourceCodeEnd":522,"githubUrl":"https://github.com/databendlabs/databend/blob/288d84d76e20a2f8f7173bda9691eb6ece301aa9/src/query/sql/src/planner/binder/location.rs#L486-L522","documentation":"parse_storage_params_from_uri requires that the path portion of the URI ends with a slash ('/') so it can be treated as a prefix/root by OpenDAL. If the URI path does not end with '/', it throws this InvalidInput error with the offending path and usage context embedded in the message.","triggerScenarios":"CREATE CONNECTION with URL='s3://bucket/prefix' (no trailing slash), or stage/connection DDL where the path component is a bare prefix; same for resolve_storage_params_from_uri callers.","commonSituations":"Writing 's3://bucket/data' instead of 's3://bucket/data/'; generating URIs programmatically and dropping the trailing slash.","solutions":["Append a trailing '/' to the URL path, e.g. URL='s3://bucket/prefix/'","If you intended the bucket root, use 's3://bucket/'","Fix URI-building code to always end the path component with '/'"],"exampleFix":"-- before\nCREATE CONNECTION c STORAGE_TYPE='s3' URL='s3://mybucket/data';\n-- after\nCREATE CONNECTION c STORAGE_TYPE='s3' URL='s3://mybucket/data/';","handlingStrategy":"validation","validationCode":"if let Some(p) = url::Url::parse(storage_url)?.path() { /* ensure trailing slash */ }\nlet path = url::Url::parse(storage_url)?.path().to_string();\nif !path.ends_with('/') && !path.is_empty() {\n    return Err(format!(\"storage URL path must end with '/': {storage_url}\"));\n}","typeGuard":null,"tryCatchPattern":"match err.kind() { ErrorKind::InvalidInput if msg.contains(\"path in URL must end with\") => /* append '/' and retry */, _ => return Err(err) }","preventionTips":["Normalize storage URIs to end paths with '/' before DDL","For bucket root, still write the trailing slash: s3://bucket/","Add a unit test for URI building helpers asserting trailing slash"],"tags":["storage","url","validation","sql"],"backgroundTag":"invalid-url-format","analyzedSha":"288d84d76e20a2f8f7173bda9691eb6ece301aa9","analyzedAt":"2026-09-11T11:29:36.208Z","contentChangedAt":"2026-09-11T11:29:36.208Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}