Quickstart
Get ROSQL running against your ROS2 telemetry data in under 5 minutes.
Install the CLI
- curl | sh
- Homebrew (macOS)
- cargo install
- Build from source
curl -fsSL https://rosql.org/install.sh | sh
Linux x86_64, Linux arm64, macOS Intel, and macOS Apple Silicon. Installs to ~/.local/bin/ with the Parquet backend included.
brew install robotopsinc/tap/rosql
Intel and Apple Silicon. Supports brew upgrade rosql and brew uninstall rosql.
# Parquet backend + CLI (recommended — no external database required)
cargo install rosql --features server,duckdb
# PostgreSQL + CLI
cargo install rosql --features server,postgres
# Both backends
cargo install rosql --features server,duckdb,postgres
All platforms including Windows. Requires Rust stable — rustup.rs.
git clone https://github.com/RobotOpsInc/rosql
cd rosql
# CLI with Parquet backend
cargo build --release --features server,duckdb --bin rosql
# CLI with PostgreSQL
cargo build --release --features server,postgres --bin rosql
Your first query — no setup required
Query our public demo dataset instantly. No data source, no credentials, no configuration:
rosql query "FROM traces WHERE status = 'ERROR' LIMIT 5" \
--backend parquet \
--url s3://robotops-production-rosql-demo/data
{
"columns": ["timestamp", "trace_id", "span_id", "span_name_col", "service_name", "duration", "status_code"],
"rows": [
[1776452455875402, "trace-amr01-m1", "span-a01-m1-root", "/navigate_to_pose", "robot-amr-01", 7000000000, "OK"]
],
"metadata": { "rows_returned": 3, "elapsed_ms": 353 }
}
The demo dataset is a simulated AMR (robot-amr-01) running three navigation missions — one successful, one battery-critical abort, one timeout. Updated on every ROSQL release.
Query your own data
The easiest way to get Parquet telemetry files to query is to run the Robot Ops demo-agent, which exports ROS2 traces, logs, metrics, and topic data as Parquet files in the expected layout.
Alternatively, if you use the Robot Ops platform, your telemetry is already available to query — no local files needed.
Once the demo-agent has run, you'll have a directory like:
./telemetry/robotops_demo_agent/20260403-141530/
traces/ *.parquet
logs/ *.parquet
metrics/ *.parquet
topic_messages/ *.parquet
mcap_metadata/ *.parquet
rosql query "FROM traces WHERE status = 'ERROR' SINCE 1 hour ago" \
--backend parquet \
--url ./telemetry/robotops_demo_agent/20260403-141530/
You can also query Parquet files directly from a private S3 bucket:
rosql query "FROM traces WHERE status = 'ERROR' SINCE 1 hour ago" \
--backend parquet \
--url s3://my-bucket/robot-01/robotops_demo_agent/20260403-141530/
See Driver Support → for S3 credential setup and all backend options.
Cross-signal correlation
The killer feature: DURING correlates events across data sources in a single query.
Find navigation failures that happened while the battery was critically low:
SELECT trace_id, span_name_col, service_name, duration, status_code, span_attributes
FROM traces
WHERE status = 'ERROR' AND action_name = '/navigate_to_pose'
DURING(
FROM topics WHERE topic_name = '/battery_state'
AND fields['percentage'] < 15
)
SINCE 6 hours ago
rosql query "SELECT trace_id, span_name_col, service_name, duration, status_code, span_attributes
FROM traces
WHERE status = 'ERROR' AND action_name = '/navigate_to_pose'
DURING(
FROM topics WHERE topic_name = '/battery_state'
AND fields['percentage'] < 15
)
SINCE 6 hours ago" \
--backend parquet \
--url ./telemetry/robotops_demo_agent/20260403-141530/
Message causality
Trace the full causality chain from any span ID:
TRACE 'a3f1c9d2e8b04f7a'
This walks parent_span_id → span_id recursively via a CTE and returns all spans in the causality chain — something plain SQL has no primitive for.
Compile to SQL
Inspect what ROSQL generates — no data source or --url needed:
rosql compile "FROM traces WHERE duration > 500 ms" --backend parquet
Output:
{
"backend": "parquet",
"ok": true,
"sql": "SELECT * FROM \"otel_traces\" WHERE \"duration\" > 500000000"
}
As a Rust library
cargo add rosql
use rosql::{parse, drivers::{sql::SqlBackend, ExecOptions}};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Parquet backend — local path or s3://
let backend = SqlBackend::from_parquet("./telemetry/robotops_demo_agent/20260403-141530/").await?;
// Or PostgreSQL
// let backend = SqlBackend::new("postgresql://user:pass@localhost/telemetry").await?;
let query = parse("
SELECT trace_id, span_name_col, service_name, duration, status_code
FROM traces WHERE status = 'ERROR' AND action_name = '/navigate_to_pose'
DURING(
FROM topics WHERE topic_name = '/battery_state' AND fields['percentage'] < 15
)
SINCE 6 hours ago
")?;
let result = backend.execute(&query, &ExecOptions::default()).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
Ok(())
}
Next steps
- Driver Support → — Parquet backend, S3 credentials, PostgreSQL, MySQL
- CLI reference → — all commands and flags
- Cookbook → — more example queries
- Schema reference → — required table structure