Skip to Content
FILE Columns & Uploads

FILE Columns & Uploads

kalam-client supports multipart SQL uploads and authenticated downloads for KalamDB FILE columns.

Enable uploads with the file-uploads feature (included in native-full):

TOML
kalam-client = { version = "0.5", features = ["native-sdk", "file-uploads"] }

Upload files with SQL

Use FILE("placeholder") in SQL and pass matching FileUpload payloads:

RUST
use kalam_client::{FileUpload, QueryParam}; let bytes = tokio::fs::read("./photo.jpg").await?;let files = vec![    FileUpload::new("photo", "photo.jpg", bytes).with_mime("image/jpeg"),]; client    .execute_with_files(        r#"INSERT INTO docs.files (id, attachment) VALUES ($1, FILE("photo"))"#,        files,        Some(vec![QueryParam::from("doc1")]),        None,    )    .await?;

Use execute_with_files_with_progress(...) when you need upload progress callbacks.

Insert + read FileRefs (end-to-end)

RUST
use kalam_client::{FileUpload, QueryParam, TableId}; // 1) Insert a FILE via multipart uploadlet files = vec![    FileUpload::new("photo", "photo.jpg", std::fs::read("photo.jpg")?)        .with_mime("image/jpeg"),];client    .execute_with_files(        r#"INSERT INTO docs.files (id, attachment) VALUES ($1, FILE("photo"))"#,        files,        Some(vec![QueryParam::from("doc1")]),        None,    )    .await?; // 2) Read rows and bind table context oncelet table_id = TableId::from_strings("docs", "files"); let response = client    .execute_query(        "SELECT id, attachment FROM docs.files WHERE id = $1",        None,        Some(vec![QueryParam::from("doc1")]),        None,    )    .await?; let cell = &response.rows().unwrap()[0][1];let bound = cell.as_bound_file(&table_id).expect("FILE column"); println!("{} ({})", bound.name, bound.format_size());println!("{}", bound.relative_url()); // 3) Download bytes through the same clientlet download = client.download_bound_file(&bound, None).await?;std::fs::write("downloaded.jpg", &download.bytes)?;

For many rows from the same table, reuse one TableId:

RUST
let table_id = TableId::from_strings("docs", "files"); for row in response.rows().unwrap() {    if let Some(bound) = row[1].as_bound_file(&table_id) {        let download = client.download_bound_file(&bound, None).await?;        // ...    }}

Why table context is separate

FILE column JSON is table-agnostic — it stores id, sub, name, size, mime, sha256, and optional shard. Namespace and table name are not embedded in stored JSON.

Download URLs require table identity:

TEXT
/v1/files/{namespace}/{table}/{sub}/{stored_name}

The SDK binds that context at parse time:

TypeRole
FileRefWire/storage metadata from the FILE column JSON
TableIdType-safe namespace + table pair from kalamdb-commons
FileRefContextWraps a TableId for download context
BoundFileRefFileRef + FileRefContext — URLs and downloads need no extra args

FileRef model

FileRef is re-exported from kalamdb-commons. Parse from query cells:

RUST
let file_ref = cell.as_file().expect("FILE column");assert!(file_ref.is_image());println!("{}", file_ref.stored_name());println!("{}", file_ref.relative_path());

Build download URLs when you already know namespace and table strings:

RUST
let url = file_ref.download_url("http://localhost:2900", "docs", "files");let path = file_ref.relative_url("docs", "files");

Prefer BoundFileRef when table identity is known — see below.

Fields and helpers

  • id, sub, name, size, mime, sha256, optional shard
  • stored_name() — sanitized filename used in HTTP paths
  • relative_path() — storage layout (sub/id-name.ext, shard-aware for shared tables)
  • format_size(), is_image(), is_pdf(), type_description(), and related MIME helpers

BoundFileRef and TableId

Attach table context when parsing cells:

RUST
use kalam_client::{BoundFileRef, FileRefContext, TableId}; let table_id = TableId::from_strings("docs", "files");let bound = cell.as_bound_file(&table_id).expect("FILE column"); // Deref to FileRef fieldsprintln!("{}", bound.name); // Table identityassert_eq!(bound.namespace(), "docs");assert_eq!(bound.table(), "files");assert_eq!(bound.table_id(), &table_id); // URLs without repeating namespace/tablelet url = bound.download_url(client.base_url());let path = bound.relative_url();

Construct manually when needed:

RUST
let ctx = FileRefContext::from_strings("docs", "files");// or: FileRefContext::new(table_id)let bound = BoundFileRef::new(file_ref, ctx);

Download APIs

MethodWhen to use
download_bound_file(&BoundFileRef, target_user_id)Preferred — context already bound
download_file(&FileRef, namespace, table, target_user_id)Legacy explicit namespace/table strings

Both return FileDownload:

  • bytes: Vec<u8>
  • content_type: Option<String>
  • content_disposition: Option<String>

For USER tables, downloads default to the authenticated caller’s scope. Pass Some("user_id") as target_user_id when a DBA or system role needs another user’s file (same rules as the HTTP API’s user_id query parameter).

Auth behavior

Downloads use the same JWT (or basic-auth exchange) as SQL queries. The client builds the request URL, applies auth headers, and returns the response body as bytes.

Example

Runnable source: File Attachments (cargo run -p file-attachments from link/sdks/rust).

Next

Last updated on