Quickstart
Get ROSQL running against your ROS2 telemetry data in under 5 minutes.
Prerequisites
- ROS2 telemetry data in an OTel-compatible database (PostgreSQL recommended)
- Rust (stable, 1.80+) — rustup.rs
Install the CLI
cargo install rosql --features server,postgres
This builds the rosql binary with the PostgreSQL driver and gRPC server. Replace postgres with mysql for MySQL/MariaDB.
Your first query
FROM traces WHERE status = 'ERROR' SINCE 1 hour ago
Run it against your database:
rosql query "FROM traces WHERE status = 'ERROR' SINCE 1 hour ago" \
--backend postgres \
--url postgresql://user:pass@localhost:5432/telemetry
{
"columns": ["timestamp", "trace_id", "span_id", "span_name_col", "service_name", "duration", "status_code"],
"rows": [
["2025-03-25T14:32:11.012Z", "a3f1c9d2...", "f0e1d2c3...", "navigate_to_pose", "bt_navigator", 1423187000, "ERROR"]
],
"metadata": { "rows_returned": 1, "elapsed_ms": 12 }
}
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 postgres \
--url postgresql://user:pass@localhost:5432/telemetry
Message causality
Trace the full causality chain from any span ID:
MESSAGE JOURNEY FOR TRACE 'a3f1c9d2e8b04f7a'
This walks parent_span_id → span_id recursively and returns all spans in the causality chain — something plain SQL has no primitive for.
Compile to SQL
Inspect what ROSQL generates (no DB needed):
rosql compile "FROM traces WHERE duration > 500 ms" --backend postgres
Output:
SELECT * FROM otel_traces
WHERE duration > 500000000
ORDER BY timestamp DESC
LIMIT 100
As a Rust library
cargo add rosql
use rosql::{parse, drivers::{SqlBackend, ExecOptions}};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
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(())
}
Building from source
git clone https://github.com/RobotOpsInc/rosql
cd rosql
# Library only (default)
cargo build --release
# CLI binary
cargo build --release --features server --bin rosql
# CLI with PostgreSQL query execution
cargo build --release --features server,postgres --bin rosql
Next steps
- Driver support → — configure your database backend
- CLI reference → — all commands and flags
- Cookbook → — more example queries
- Schema reference → — required table structure