Schema Reference
ROSQL expects telemetry data in the OpenTelemetry schema conventions for ROS2. This document defines the complete schema: table definitions, span attributes, field mappings, and schema profiles.
If you're building a ROS2-to-OTel bridge or setting up your own telemetry storage, implement these conventions to get full ROSQL feature support.
The table names are the same across all backends. What differs is the physical layout — column naming conventions for SQL databases, or directory structure for the Parquet backend.
Span attributes
ROS2 action executions
| Attribute | Type | Example | Used by ROSQL for |
|---|---|---|---|
ros.node | string | /bt_navigator | WHERE node = '...' |
ros.action.name | string | /navigate_to_pose | WHERE action_name = '...' |
ros.action.type | string | nav2_msgs/action/NavigateToPose | Action type filtering |
ros.action.goal_id | string | UUID | Goal correlation |
ros.action.status | string | succeeded / aborted / canceled | Action status filtering |
ROS2 pub/sub tracing
| Attribute | Type | Example | Used by ROSQL for |
|---|---|---|---|
ros.topic | string | /cmd_vel | Topic filtering, MESSAGE FLOW |
ros.message_type | string | geometry_msgs/msg/Twist | Message type filtering |
ros.publisher_node | string | /controller_server | Publisher attribution |
ros.subscriber_node | string | /motor_driver | Subscriber attribution |
ros.session.id | string | sess_abc123 | FOR SESSION scoping |
Universal scope resource attributes
These resource attributes are set at the SDK/bridge level and enable FOR clause scoping across all query types.
| Attribute | Type | Example | Used by ROSQL for |
|---|---|---|---|
robot.id | string | robot_42 | FOR ROBOT scoping |
service.version | string | v1.2.3 | FOR VERSION scoping |
deployment.environment | string | production | FOR ENVIRONMENT scoping |
ros.session.id | string | sess_abc123 | FOR SESSION scoping |
ParentSpanId convention for TRACE
The publish span's SpanId must be set as the ParentSpanId of the corresponding subscribe span. This creates the causal chain that TRACE traverses recursively via a CTE.
Publisher span (SpanId: "abc")
└── Subscriber span (ParentSpanId: "abc", SpanId: "def")
└── Downstream span (ParentSpanId: "def", SpanId: "ghi")
Implementing this correctly requires middleware-level instrumentation — it cannot be done purely at the application level without code changes to every node.
Metric naming conventions
| Metric name | Unit | Description |
|---|---|---|
ros2.topic.message_rate | Hz | Topic publish rate |
ros2.topic.bandwidth | B/s | Topic bandwidth |
ros2.topic.messages_received | — | Messages received by subscribers |
ros2.topic.messages_captured | — | Messages captured (e.g. by recorder) |
ros2.topic.messages_filtered | — | Messages filtered/dropped |
ros2.action_servers.count | — | Active action server count |
ros2.services.count | — | Active service count |
ros2.action.queued_goals | — | Queued action goals |
ros2.action.active_goals | — | Active action goals |
ros2.action.completion_rate | Hz | Action completion rate |
system.cpu.utilization | % | Total CPU utilization |
system.memory.utilization | % | Memory utilization |
system.memory.usage | B | Memory usage in bytes |
system.filesystem.utilization | % | Disk utilization |
system.filesystem.usage | B | Disk usage in bytes |
system.disk.io | B/s | Disk I/O throughput |
system.disk.operations | ops/s | Disk IOPS |
system.network.io | B/s | Network I/O throughput |
system.network.packets | packets/s | Network packet rate |
system.network.latency | ms | Network latency |
system.network.jitter | ms | Network jitter |
system.network.packet_loss | % | Packet loss rate |
system.temperature | °C | System temperature |
system.battery.charge | % | Battery state of charge |
system.battery.voltage | V | Battery voltage |
system.battery.current | A | Battery current draw |
system.battery.temperature | °C | Battery temperature |
process.cpu.utilization | % | Per-process CPU utilization |
process.memory.usage | B | Per-process memory usage |
Required tables
These three tables are required for basic ROSQL functionality.
otel_traces
Used by: FROM traces, TRACE, MESSAGE FLOW, DURING(), ACTION_SUCCESS_RATE, MOVING_AVG, DERIVATIVE, SHOW DEPLOYMENTS, SHOW SPAN SUMMARY, SHOW PLANS
- PostgreSQL
- ClickHouse
- Parquet
CREATE TABLE otel_traces (
timestamp TIMESTAMPTZ NOT NULL,
trace_id TEXT NOT NULL,
span_id TEXT NOT NULL,
parent_span_id TEXT NOT NULL DEFAULT '',
span_name_col TEXT NOT NULL,
span_kind TEXT NOT NULL DEFAULT 'INTERNAL',
service_name TEXT NOT NULL DEFAULT '',
duration BIGINT NOT NULL, -- nanoseconds
status_code TEXT NOT NULL DEFAULT 'OK',
span_attributes JSONB NOT NULL DEFAULT '{}',
resource_attributes JSONB NOT NULL DEFAULT '{}'
);
Use --schema otel-postgres (default for PostgreSQL and MySQL).
CREATE TABLE otel_traces (
Timestamp DateTime64(9) NOT NULL,
TraceId String NOT NULL,
SpanId String NOT NULL,
ParentSpanId String NOT NULL DEFAULT '',
SpanName String NOT NULL,
SpanKind String NOT NULL DEFAULT 'INTERNAL',
ServiceName String NOT NULL DEFAULT '',
Duration Int64 NOT NULL, -- nanoseconds
StatusCode String NOT NULL DEFAULT 'OK',
SpanAttributes Map(String, String) NOT NULL,
ResourceAttributes Map(String, String) NOT NULL
) ENGINE = MergeTree()
ORDER BY (Timestamp, TraceId, SpanId);
Use --schema otel-clickhouse.
Create a traces/ subdirectory under your base URL. ROSQL creates a DuckDB view over traces/**/*.parquet.
<url>/
traces/
*.parquet ← or any nested structure, e.g. date=2026-04-11/*.parquet
Required Parquet columns (snake_case, matching the otel-postgres profile):
| Column | Parquet type |
|---|---|
timestamp | TIMESTAMP (UTC) |
trace_id | STRING |
span_id | STRING |
parent_span_id | STRING |
span_name_col | STRING |
span_kind | STRING |
service_name | STRING |
duration | INT64 (nanoseconds) |
status_code | STRING |
span_attributes | STRING (JSON) |
resource_attributes | STRING (JSON) |
If the traces/ subdirectory is absent or contains no .parquet files, ROSQL silently skips the view and reports FROM traces as unavailable.
otel_logs
Used by: FROM logs
- PostgreSQL
- ClickHouse
- Parquet
CREATE TABLE otel_logs (
timestamp TIMESTAMPTZ NOT NULL,
trace_id TEXT NOT NULL DEFAULT '',
span_id TEXT NOT NULL DEFAULT '',
severity_text TEXT NOT NULL DEFAULT 'INFO',
severity_number INTEGER NOT NULL DEFAULT 9,
service_name TEXT NOT NULL DEFAULT '',
body TEXT NOT NULL DEFAULT '',
resource_attributes JSONB NOT NULL DEFAULT '{}',
log_attributes JSONB NOT NULL DEFAULT '{}'
);
CREATE TABLE otel_logs (
Timestamp DateTime64(9) NOT NULL,
TraceId String NOT NULL DEFAULT '',
SpanId String NOT NULL DEFAULT '',
SeverityText String NOT NULL DEFAULT 'INFO',
SeverityNumber UInt8 NOT NULL DEFAULT 9,
ServiceName String NOT NULL DEFAULT '',
Body String NOT NULL DEFAULT '',
ResourceAttributes Map(String, String) NOT NULL,
LogAttributes Map(String, String) NOT NULL
) ENGINE = MergeTree()
ORDER BY Timestamp;
Subdirectory: logs/. ROSQL creates a view over logs/**/*.parquet.
| Column | Parquet type |
|---|---|
timestamp | TIMESTAMP (UTC) |
trace_id | STRING |
span_id | STRING |
severity_text | STRING |
severity_number | INT32 |
service_name | STRING |
body | STRING |
resource_attributes | STRING (JSON) |
log_attributes | STRING (JSON) |
otel_metrics
Used by: FROM metrics, TOPIC_RATE
- PostgreSQL
- ClickHouse
- Parquet
CREATE TABLE otel_metrics (
timestamp TIMESTAMPTZ NOT NULL,
metric_name TEXT NOT NULL,
value DOUBLE PRECISION NOT NULL,
attributes JSONB NOT NULL DEFAULT '{}',
service_name TEXT NOT NULL DEFAULT ''
);
CREATE TABLE otel_metrics (
Timestamp DateTime64(9) NOT NULL,
MetricName String NOT NULL,
Value Float64 NOT NULL,
Attributes Map(String, String) NOT NULL,
ServiceName String NOT NULL DEFAULT ''
) ENGINE = MergeTree()
ORDER BY (Timestamp, MetricName);
Subdirectory: metrics/. ROSQL creates a view over metrics/**/*.parquet.
| Column | Parquet type |
|---|---|
timestamp | TIMESTAMP (UTC) |
metric_name | STRING |
value | DOUBLE |
attributes | STRING (JSON) |
service_name | STRING |
Optional tables
These tables enable additional ROSQL features. If absent, ROSQL returns a clear DataSourceUnavailable error with guidance.
topic_messages
Used by: FROM topics, FROM odom (and other topic aliases)
- PostgreSQL
- ClickHouse
- Parquet
CREATE TABLE topic_messages (
robot_id TEXT NOT NULL,
topic_name TEXT NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
fields JSONB NOT NULL DEFAULT '{}',
message_type TEXT NOT NULL DEFAULT ''
);
CREATE TABLE topic_messages (
robot_id String NOT NULL,
topic_name String NOT NULL,
Timestamp DateTime64(9) NOT NULL,
fields String NOT NULL DEFAULT '{}',
message_type String NOT NULL DEFAULT ''
) ENGINE = MergeTree()
ORDER BY (Timestamp, topic_name, robot_id);
Subdirectory: topic_messages/. ROSQL creates a view over topic_messages/**/*.parquet.
| Column | Parquet type |
|---|---|
robot_id | STRING |
topic_name | STRING |
timestamp | TIMESTAMP (UTC) |
fields | STRING (JSON) |
message_type | STRING |
mcap_metadata
Used by: FROM recordings, FROM recordings WHERE topic = '...'
- PostgreSQL
- ClickHouse
- Parquet
CREATE TABLE mcap_metadata (
robot_id TEXT NOT NULL,
session_id TEXT NOT NULL,
start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ NOT NULL,
s3_key TEXT NOT NULL,
topics TEXT[] NOT NULL DEFAULT '{}',
message_types JSONB NOT NULL DEFAULT '{}' -- topic → message_type map
);
CREATE TABLE mcap_metadata (
robot_id String NOT NULL,
session_id String NOT NULL,
start_time DateTime64(9) NOT NULL,
end_time DateTime64(9) NOT NULL,
s3_key String NOT NULL,
topics Array(String) NOT NULL DEFAULT [],
message_types String NOT NULL DEFAULT '{}' -- topic → message_type JSON
) ENGINE = MergeTree()
ORDER BY (start_time, robot_id);
Subdirectory: mcap_metadata/. ROSQL creates a view over mcap_metadata/**/*.parquet.
| Column | Parquet type |
|---|---|
robot_id | STRING |
session_id | STRING |
start_time | TIMESTAMP (UTC) |
end_time | TIMESTAMP (UTC) |
s3_key | STRING |
topics | LIST<STRING> |
message_types | STRING (JSON) |
robot_joint_map (v0.4.3+)
Stores URDF-derived joint metadata per robot model. Required for SHOW JOINTS.
Not available in the Parquet backend — requires a persistent database table.
- PostgreSQL
- ClickHouse
CREATE TABLE robot_joint_map (
robot_model TEXT NOT NULL,
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_to TIMESTAMPTZ, -- NULL = currently active
version TEXT NOT NULL DEFAULT '',
robot_ids TEXT[] NOT NULL DEFAULT '{}', -- robots using this model
joint_map JSONB NOT NULL DEFAULT '[]' -- array of joint descriptors
);
CREATE TABLE robot_joint_map (
robot_model String NOT NULL,
valid_from DateTime64(9) NOT NULL,
valid_to Nullable(DateTime64(9)),
version String NOT NULL DEFAULT '',
robot_ids Array(String) NOT NULL DEFAULT [],
joint_map String NOT NULL DEFAULT '[]' -- JSON array of joint descriptors
) ENGINE = ReplacingMergeTree(valid_from)
ORDER BY (robot_model, valid_from);
joint_map element schema:
{
"joint_name": "shoulder_pan_joint",
"joint_index": 0,
"joint_type": "revolute",
"lower_limit": -3.14159,
"upper_limit": 3.14159
}
Used by: SHOW JOINTS, JOINT DEVIATION
Additional tables (optional)
These tables power the additional data sources. If absent, ROSQL returns a DataSourceUnavailable error.
These tables are supported in PostgreSQL and MySQL only. They are not part of the standard Parquet directory layout.
| ROSQL source | Table | Description |
|---|---|---|
FROM tf | tf_states | ROS2 TF transform data |
FROM heartbeats | robot_heartbeats | Robot heartbeat / liveness data |
FROM system_logs | system_logs | System-level log events (OS/kernel) |
FROM diagnostics | otel_metrics | ROS2 diagnostic messages (maps to metrics) |
FROM events | ros2_events | ROS2 lifecycle and state machine events |
ROSQL field mappings
ROSQL field names are resolved to physical column names based on the active schema profile. Use --schema otel-postgres (default) or --schema otel-clickhouse.
The Parquet backend uses otel-postgres column names.
From otel_traces
- otel-postgres
- otel-clickhouse
| ROSQL field | Column | Storage unit |
|---|---|---|
trace_id | trace_id | — |
span_id | span_id | — |
parent_span_id | parent_span_id | — |
span_name | span_name_col | — |
service | service_name | — |
duration | duration | nanoseconds |
status | status_code | OK / ERROR |
node | span_attributes->>'ros.node' | — |
action_name | span_attributes->>'ros.action.name' | — |
action_status | span_attributes->>'ros.action.status' | — |
topic | span_attributes->>'ros.topic' | — |
robot_id | resource_attributes->>'robot.id' | — |
version | resource_attributes->>'service.version' | — |
environment | resource_attributes->>'deployment.environment' | — |
session_id | resource_attributes->>'ros.session.id' | — |
| ROSQL field | Column | Storage unit |
|---|---|---|
trace_id | TraceId | — |
span_id | SpanId | — |
parent_span_id | ParentSpanId | — |
span_name | SpanName | — |
service | ServiceName | — |
duration | Duration | nanoseconds |
status | StatusCode | OK / ERROR |
node | SpanAttributes['ros.node'] | — |
action_name | SpanAttributes['ros.action.name'] | — |
action_status | SpanAttributes['ros.action.status'] | — |
topic | SpanAttributes['ros.topic'] | — |
robot_id | ResourceAttributes['robot.id'] | — |
version | ResourceAttributes['service.version'] | — |
environment | ResourceAttributes['deployment.environment'] | — |
session_id | ResourceAttributes['ros.session.id'] | — |
From otel_logs
- otel-postgres
- otel-clickhouse
| ROSQL field | Column |
|---|---|
message | body |
severity | severity_text |
severity_number | severity_number |
| ROSQL field | Column |
|---|---|
message | Body |
severity | SeverityText |
severity_number | SeverityNumber |
From otel_metrics
- otel-postgres
- otel-clickhouse
| ROSQL field | Resolved as | Storage unit |
|---|---|---|
metric_name | metric_name | — |
metric_value | value | varies |
publish_rate | value WHERE metric_name = 'ros2.topic.message_rate' | Hz |
bandwidth | value WHERE metric_name = 'ros2.topic.bandwidth' | B/s |
messages_received | value WHERE metric_name = 'ros2.topic.messages_received' | — |
messages_captured | value WHERE metric_name = 'ros2.topic.messages_captured' | — |
messages_filtered | value WHERE metric_name = 'ros2.topic.messages_filtered' | — |
action_servers_count | value WHERE metric_name = 'ros2.action_servers.count' | — |
services_count | value WHERE metric_name = 'ros2.services.count' | — |
queued_goals | value WHERE metric_name = 'ros2.action.queued_goals' | — |
active_goals | value WHERE metric_name = 'ros2.action.active_goals' | — |
completion_rate | value WHERE metric_name = 'ros2.action.completion_rate' | Hz |
cpu_usage | value WHERE metric_name = 'system.cpu.utilization' | % |
memory_usage | value WHERE metric_name = 'system.memory.utilization' | % |
memory_bytes | value WHERE metric_name = 'system.memory.usage' | B |
disk_usage | value WHERE metric_name = 'system.filesystem.utilization' | % |
disk_bytes | value WHERE metric_name = 'system.filesystem.usage' | B |
disk_io | value WHERE metric_name = 'system.disk.io' | B/s |
disk_iops | value WHERE metric_name = 'system.disk.operations' | ops/s |
network_io | value WHERE metric_name = 'system.network.io' | B/s |
network_packets | value WHERE metric_name = 'system.network.packets' | packets/s |
network_latency | value WHERE metric_name = 'system.network.latency' | ms |
network_jitter | value WHERE metric_name = 'system.network.jitter' | ms |
packet_loss | value WHERE metric_name = 'system.network.packet_loss' | % |
temperature | value WHERE metric_name = 'system.temperature' | °C |
battery_charge | value WHERE metric_name = 'system.battery.charge' | % |
battery_voltage | value WHERE metric_name = 'system.battery.voltage' | V |
battery_current | value WHERE metric_name = 'system.battery.current' | A |
battery_temperature | value WHERE metric_name = 'system.battery.temperature' | °C |
process_cpu | value WHERE metric_name = 'process.cpu.utilization' | % |
process_memory | value WHERE metric_name = 'process.memory.usage' | B |
| ROSQL field | Resolved as | Storage unit |
|---|---|---|
metric_name | MetricName | — |
metric_value | Value | varies |
publish_rate | Value WHERE MetricName = 'ros2.topic.message_rate' | Hz |
bandwidth | Value WHERE MetricName = 'ros2.topic.bandwidth' | B/s |
messages_received | Value WHERE MetricName = 'ros2.topic.messages_received' | — |
messages_captured | Value WHERE MetricName = 'ros2.topic.messages_captured' | — |
messages_filtered | Value WHERE MetricName = 'ros2.topic.messages_filtered' | — |
action_servers_count | Value WHERE MetricName = 'ros2.action_servers.count' | — |
services_count | Value WHERE MetricName = 'ros2.services.count' | — |
queued_goals | Value WHERE MetricName = 'ros2.action.queued_goals' | — |
active_goals | Value WHERE MetricName = 'ros2.action.active_goals' | — |
completion_rate | Value WHERE MetricName = 'ros2.action.completion_rate' | Hz |
cpu_usage | Value WHERE MetricName = 'system.cpu.utilization' | % |
memory_usage | Value WHERE MetricName = 'system.memory.utilization' | % |
memory_bytes | Value WHERE MetricName = 'system.memory.usage' | B |
disk_usage | Value WHERE MetricName = 'system.filesystem.utilization' | % |
disk_bytes | Value WHERE MetricName = 'system.filesystem.usage' | B |
disk_io | Value WHERE MetricName = 'system.disk.io' | B/s |
disk_iops | Value WHERE MetricName = 'system.disk.operations' | ops/s |
network_io | Value WHERE MetricName = 'system.network.io' | B/s |
network_packets | Value WHERE MetricName = 'system.network.packets' | packets/s |
network_latency | Value WHERE MetricName = 'system.network.latency' | ms |
network_jitter | Value WHERE MetricName = 'system.network.jitter' | ms |
packet_loss | Value WHERE MetricName = 'system.network.packet_loss' | % |
temperature | Value WHERE MetricName = 'system.temperature' | °C |
battery_charge | Value WHERE MetricName = 'system.battery.charge' | % |
battery_voltage | Value WHERE MetricName = 'system.battery.voltage' | V |
battery_current | Value WHERE MetricName = 'system.battery.current' | A |
battery_temperature | Value WHERE MetricName = 'system.battery.temperature' | °C |
process_cpu | Value WHERE MetricName = 'process.cpu.utilization' | % |
process_memory | Value WHERE MetricName = 'process.memory.usage' | B |
From topic_messages (odom / position data)
ROSQL geospatial operators (WITHIN) and JOINT DEVIATION extract nested values from the fields JSONB column using these conventions:
| ROSQL path | fields JSON path | Description |
|---|---|---|
position.latitude | fields->>'position.latitude' | GPS latitude (degrees) |
position.longitude | fields->>'position.longitude' | GPS longitude (degrees) |
pose.pose.position.x | fields->>'pose.pose.position.x' | Local frame X (metres) |
pose.pose.position.y | fields->>'pose.pose.position.y' | Local frame Y (metres) |
position[N] | fields->'position'->>N | Joint position array element N (radians) |
These match the standard nav_msgs/Odometry and sensor_msgs/JointState ROS2 message layouts.
Topic aliases
| ROSQL source | Resolves to |
|---|---|
FROM odom | FROM topics WHERE topic_name = '/odom' |
FROM joint_states | FROM topics WHERE topic_name = '/joint_states' |
FROM battery | FROM topics WHERE topic_name = '/battery_state' |
FROM cmd_vel | FROM topics WHERE topic_name = '/cmd_vel' |
FROM imu | FROM topics WHERE topic_name = '/imu/data' |