use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
use arrow::array::RecordBatch;
use datafusion::datasource::TableProvider as _;
use datafusion::physical_plan::ExecutionPlanProperties as _;
use datafusion::prelude::SessionContext;
use futures::{StreamExt as _, TryStreamExt as _};
use itertools::Itertools as _;
use re_chunk_store::IndexValue;
use re_datafusion::DataframeQueryTableProvider;
use re_log_types::{EntityPath, TimeInt, TimeType};
use re_protos::cloud::v1alpha1::ext;
use re_protos::cloud::v1alpha1::ext::DatasetEntry;
use re_protos::cloud::v1alpha1::ext::QueryDatasetDataframe;
use re_protos::cloud::v1alpha1::rerun_cloud_service_server::RerunCloudService;
use re_types_core::SegmentId;
use crate::RecordBatchTestExt as _;
use crate::tests::common::{
DataSourcesDefinition, LayerDefinition, RerunCloudServiceExt as _, concat_record_batches, prop,
};
use crate::utils::client::TestClient;
pub async fn query_dataset_index_values_by_time_type<T: RerunCloudService>(
service: Arc<T>,
time_type: TimeType,
) {
let tuid_prefix = match time_type {
TimeType::TimestampNs => 1,
TimeType::DurationNs => 10,
TimeType::Sequence => 20,
};
let data_sources_def = DataSourcesDefinition::new_with_tuid_prefix(
tuid_prefix,
[
LayerDefinition::simple_with_time(
"my_segment_id1",
&["my/entity", "my/other/entity"],
1000,
time_type,
),
LayerDefinition::simple_with_time("my_segment_id2", &["my/entity"], 2000, time_type),
LayerDefinition::properties(
"my_segment_id1",
[prop(
"text_log",
re_sdk_types::archetypes::TextLog::new("i'm segment 1"),
)],
)
.layer_name("props"),
LayerDefinition::simple_with_time(
"my_segment_id3",
&["my/entity", "another/one", "yet/another/one"],
3000,
time_type,
),
],
);
let dataset_name = format!("dataset_{time_type}");
let dataset_entry = service.create_dataset_entry_with_name(&dataset_name).await;
service
.register_with_dataset_name_blocking(&dataset_name, data_sources_def.to_data_sources())
.await;
let client = TestClient::new(service);
let tests = vec![
(
vec![
("my_segment_id1", vec![1020, 1040]),
("my_segment_id2", vec![2010, 2030]),
("my_segment_id3", vec![3010, 3020, 3030, 3040]),
],
"all_valid_index_values",
true,
),
(
vec![("my_segment_id1", vec![1020, 1040])],
"single_segment",
false,
),
(
vec![("my_segment_id4", vec![1020, 1040])],
"unknown_segment",
false,
),
];
for (index_values, snapshot_name, check_schema) in tests {
query_dataset_snapshot(
client.clone(),
&dataset_entry,
index_values,
&format!("query_index_values_{time_type}_{snapshot_name}"),
time_type,
check_schema,
)
.await;
}
}
pub async fn query_dataset_index_values(service: impl RerunCloudService) {
let service = Arc::new(service);
query_dataset_index_values_by_time_type(service.clone(), TimeType::Sequence).await;
query_dataset_index_values_by_time_type(service.clone(), TimeType::DurationNs).await;
query_dataset_index_values_by_time_type(service.clone(), TimeType::TimestampNs).await;
}
async fn per_segment_chunk_id_set<T: RerunCloudService>(
service: &T,
dataset_name: &str,
request: ext::QueryDatasetRequest,
) -> BTreeSet<re_chunk::ChunkId> {
use re_protos::cloud::v1alpha1::QueryDatasetResponse;
use re_protos::headers::RerunHeadersInjectorExt as _;
let stream = service
.query_dataset(
tonic::Request::new(request.into())
.with_entry_name(crate::tests::common::entry_name(dataset_name)),
)
.await
.unwrap()
.into_inner();
let mut stream = Box::pin(stream);
let mut ids: BTreeSet<re_chunk::ChunkId> = BTreeSet::new();
while let Some(resp) = stream.next().await {
let resp: QueryDatasetResponse = resp.unwrap();
if let Some(part) = resp.data {
let batch: arrow::array::RecordBatch = part.try_into().unwrap();
let id_col = QueryDatasetDataframe::COLUMN_CHUNK_ID
.extract(&batch)
.expect("bad chunk_id column in response");
ids.extend(id_col.iter_owned());
}
}
ids
}
async fn register_per_segment_dataset(
service: &impl RerunCloudService,
dataset_name: &str,
tuid_prefix: u64,
time_type: TimeType,
) -> DataSourcesDefinition {
let data_sources_def = DataSourcesDefinition::new_with_tuid_prefix(
tuid_prefix,
[
LayerDefinition::simple_one_chunk_per_frame_with_time(
"seg1",
&["my/entity"],
1000,
time_type,
),
LayerDefinition::simple_one_chunk_per_frame_with_time(
"seg2",
&["my/entity"],
2000,
time_type,
),
LayerDefinition::simple_one_chunk_per_frame_with_time(
"seg3",
&["my/entity"],
3000,
time_type,
),
],
);
service.create_dataset_entry_with_name(dataset_name).await;
service
.register_with_dataset_name_blocking(dataset_name, data_sources_def.to_data_sources())
.await;
data_sources_def
}
fn per_segment_segment_ids() -> Vec<re_types_core::SegmentId> {
vec!["seg1".into(), "seg2".into(), "seg3".into()]
}
pub async fn query_dataset_per_segment_values_wire_level(service: impl RerunCloudService) {
let service = Arc::new(service);
for time_type in [
TimeType::Sequence,
TimeType::DurationNs,
TimeType::TimestampNs,
] {
query_dataset_per_segment_values_wire_level_by_time_type(service.as_ref(), time_type).await;
}
}
async fn query_dataset_per_segment_values_wire_level_by_time_type<T: RerunCloudService>(
service: &T,
time_type: TimeType,
) {
use re_protos::cloud::v1alpha1::ext::QueryDatasetRequest;
let dataset_name = &format!("per_segment_wire_level_{time_type}");
let tuid_prefix = match time_type {
TimeType::Sequence => 77,
TimeType::DurationNs => 78,
TimeType::TimestampNs => 79,
};
let _data_sources =
register_per_segment_dataset(service, dataset_name, tuid_prefix, time_type).await;
let index_name = re_log_types::build_index_value(0_i64, time_type)
.0
.name()
.as_str();
let segment_ids = per_segment_segment_ids();
let range_baseline_request = QueryDatasetRequest {
segment_ids: segment_ids.clone(),
select_all_entity_paths: true,
query: Some(ext::Query {
range: Some(ext::QueryRange {
index: index_name.into(),
index_range: re_log_types::AbsoluteTimeRange::EVERYTHING,
}),
..Default::default()
}),
..Default::default()
};
let range_baseline_ids =
per_segment_chunk_id_set(service, dataset_name, range_baseline_request).await;
assert!(
!range_baseline_ids.is_empty(),
"range baseline must return at least one chunk, got 0"
);
let latest_at_baseline_request = QueryDatasetRequest {
segment_ids: segment_ids.clone(),
select_all_entity_paths: true,
query: Some(ext::Query {
latest_at: Some(ext::QueryLatestAt {
index: Some(index_name.into()),
at: TimeInt::MAX,
per_segment_values: vec![],
}),
..Default::default()
}),
..Default::default()
};
let latest_at_baseline_ids =
per_segment_chunk_id_set(service, dataset_name, latest_at_baseline_request).await;
assert!(
!latest_at_baseline_ids.is_empty(),
"latest_at(MAX) baseline must return at least one chunk, got 0"
);
let filtered_request = QueryDatasetRequest {
segment_ids: segment_ids.clone(),
select_all_entity_paths: true,
query: Some(ext::Query {
latest_at: Some(ext::QueryLatestAt {
index: Some(index_name.into()),
at: TimeInt::STATIC,
per_segment_values: vec![
vec![1010], vec![2010], vec![3010], ],
}),
..Default::default()
}),
..Default::default()
};
let filtered_ids = per_segment_chunk_id_set(service, dataset_name, filtered_request).await;
assert!(
!filtered_ids.is_empty(),
"per_segment_values filter must still return matched chunks, got 0"
);
assert!(
filtered_ids.is_subset(&range_baseline_ids),
"per_segment_values must only return chunks already in the range baseline; \
got {} extra chunk(s)",
filtered_ids.difference(&range_baseline_ids).count(),
);
assert!(
filtered_ids.len() < range_baseline_ids.len(),
"per_segment_values must narrow the result set strictly below the range baseline; \
range_baseline={} filtered={}",
range_baseline_ids.len(),
filtered_ids.len(),
);
assert!(
!filtered_ids.is_subset(&latest_at_baseline_ids),
"per_segment_values must select different temporal chunks than latest_at(MAX); \
filtered={filtered_ids:?} latest_at_baseline={latest_at_baseline_ids:?}",
);
let static_only_request = QueryDatasetRequest {
segment_ids: segment_ids.clone(),
select_all_entity_paths: true,
query: Some(ext::Query {
latest_at: Some(ext::QueryLatestAt {
index: Some(index_name.into()),
at: TimeInt::STATIC,
per_segment_values: vec![vec![], vec![], vec![]],
}),
..Default::default()
}),
..Default::default()
};
let static_only_ids =
per_segment_chunk_id_set(service, dataset_name, static_only_request).await;
assert!(
static_only_ids.len() <= filtered_ids.len(),
"empty per_segment_values must return no MORE chunks than the temporal filter, \
got static={} filtered={}",
static_only_ids.len(),
filtered_ids.len(),
);
assert!(
!static_only_ids.is_empty(),
"empty per_segment_values must still surface static chunks; got 0",
);
assert_eq!(
static_only_ids.len(),
3,
"fixture has 3 static chunks (one per segment); got {}",
static_only_ids.len(),
);
assert!(
static_only_ids.is_subset(&range_baseline_ids),
"static-only result must be a subset of the range baseline chunks",
);
assert!(
static_only_ids.is_subset(&filtered_ids),
"static-only chunks must also appear in the single-value filtered result",
);
}
pub async fn query_dataset_emits_per_segment_pushdown(service: impl RerunCloudService) {
let service = Arc::new(service);
for time_type in [
TimeType::Sequence,
TimeType::DurationNs,
TimeType::TimestampNs,
] {
query_dataset_emits_per_segment_pushdown_by_time_type(&service, time_type).await;
}
}
async fn query_dataset_emits_per_segment_pushdown_by_time_type<T: RerunCloudService>(
service: &Arc<T>,
time_type: TimeType,
) {
use re_protos::cloud::v1alpha1::ext::QueryDatasetRequest;
let dataset_name = format!("client_pushdown_{time_type}");
let tuid_prefix = match time_type {
TimeType::Sequence => 88,
TimeType::DurationNs => 89,
TimeType::TimestampNs => 90,
};
let data_sources_def = DataSourcesDefinition::new_with_tuid_prefix(
tuid_prefix,
[
LayerDefinition::simple_one_chunk_per_frame_with_time(
"seg1",
&["my/entity"],
1000,
time_type,
),
LayerDefinition::simple_one_chunk_per_frame_with_time(
"seg2",
&["my/entity"],
2000,
time_type,
),
LayerDefinition::simple_one_chunk_per_frame_with_time(
"seg3",
&["my/entity"],
3000,
time_type,
),
],
);
let dataset_entry = service.create_dataset_entry_with_name(&dataset_name).await;
service
.register_with_dataset_name_blocking(&dataset_name, data_sources_def.to_data_sources())
.await;
let _data_sources = data_sources_def;
let segment_ids = ["seg1", "seg2", "seg3"];
let index_values: BTreeMap<SegmentId, BTreeSet<IndexValue>> = [
(SegmentId::from("seg1"), 1010),
(SegmentId::from("seg2"), 2010),
(SegmentId::from("seg3"), 3010),
]
.into_iter()
.map(|(seg, value)| (seg, BTreeSet::from([TimeInt::new_temporal(value)])))
.collect();
let index_name = re_log_types::build_index_value(0_i64, time_type)
.0
.name()
.as_str();
let query = re_chunk_store::QueryExpression {
view_contents: Some(std::iter::once((EntityPath::from("my/entity"), None)).collect()),
filtered_index: Some(index_name.into()),
..Default::default()
};
async fn emitted_per_segment_values<T: RerunCloudService>(
service: &Arc<T>,
dataset_id: re_log_types::EntryId,
query: &re_chunk_store::QueryExpression,
segment_ids: &[&str],
index_values: &BTreeMap<SegmentId, BTreeSet<IndexValue>>,
) -> BTreeMap<SegmentId, Vec<i64>> {
let client = TestClient::new(Arc::clone(service));
let provider = DataframeQueryTableProvider::new_from_client(
client.clone(),
dataset_id,
query,
segment_ids,
Some(Arc::new(index_values.clone())),
None,
None,
Vec::new(),
)
.await
.unwrap();
let ctx = SessionContext::default();
let _plan = provider.scan(&ctx.state(), None, &[], None).await.unwrap();
let captured = client.query_dataset_requests.lock().clone();
assert!(
!captured.is_empty(),
"scan must emit at least one query_dataset request"
);
let mut emitted: BTreeMap<SegmentId, Vec<i64>> = BTreeMap::new();
for wire in &captured {
let request = QueryDatasetRequest::try_from(wire.clone())
.expect("emitted request must pass server wire validation");
let Some(latest_at) = request.query.as_ref().and_then(|q| q.latest_at.as_ref()) else {
continue;
};
for (segment_id, values) in
std::iter::zip(&request.segment_ids, &latest_at.per_segment_values)
{
if !values.is_empty() {
emitted.insert(segment_id.clone(), values.clone());
}
}
}
emitted
}
let pushed = emitted_per_segment_values(
service,
dataset_entry.details.id,
&query,
&segment_ids,
&index_values,
)
.await;
let expected: BTreeMap<SegmentId, Vec<i64>> = [
(SegmentId::from("seg1"), vec![1010]),
(SegmentId::from("seg2"), vec![2010]),
(SegmentId::from("seg3"), vec![3010]),
]
.into_iter()
.collect();
assert_eq!(
pushed, expected,
"pushdown must emit per_segment_values aligned to segment_ids"
);
}
pub async fn query_dataset_per_segment_values_multi_value_wire_level(
service: impl RerunCloudService,
) {
let service = Arc::new(service);
for time_type in [
TimeType::Sequence,
TimeType::DurationNs,
TimeType::TimestampNs,
] {
query_dataset_per_segment_values_multi_value_wire_level_by_time_type(
service.as_ref(),
time_type,
)
.await;
}
}
async fn query_dataset_per_segment_values_multi_value_wire_level_by_time_type<
T: RerunCloudService,
>(
service: &T,
time_type: TimeType,
) {
use re_protos::cloud::v1alpha1::ext::QueryDatasetRequest;
let dataset_name = &format!("per_segment_multi_value_wire_level_{time_type}");
let tuid_prefix = match time_type {
TimeType::Sequence => 88,
TimeType::DurationNs => 89,
TimeType::TimestampNs => 90,
};
let _data_sources =
register_per_segment_dataset(service, dataset_name, tuid_prefix, time_type).await;
let index_name = re_log_types::build_index_value(0_i64, time_type)
.0
.name()
.as_str();
let segment_ids = per_segment_segment_ids();
let range_baseline_request = QueryDatasetRequest {
segment_ids: segment_ids.clone(),
select_all_entity_paths: true,
query: Some(ext::Query {
range: Some(ext::QueryRange {
index: index_name.into(),
index_range: re_log_types::AbsoluteTimeRange::EVERYTHING,
}),
..Default::default()
}),
..Default::default()
};
let range_baseline_ids =
per_segment_chunk_id_set(service, dataset_name, range_baseline_request).await;
let filtered_request = QueryDatasetRequest {
segment_ids: segment_ids.clone(),
select_all_entity_paths: true,
query: Some(ext::Query {
latest_at: Some(ext::QueryLatestAt {
index: Some(index_name.into()),
at: TimeInt::STATIC,
per_segment_values: vec![
vec![1010, 1030], vec![2020], vec![3010, 3030], ],
}),
..Default::default()
}),
..Default::default()
};
let filtered_ids = per_segment_chunk_id_set(service, dataset_name, filtered_request).await;
assert!(
filtered_ids.is_subset(&range_baseline_ids),
"multi-value per_segment_values must only return chunks in the range baseline; \
got {} extra chunk(s)",
filtered_ids.difference(&range_baseline_ids).count(),
);
assert!(
filtered_ids.len() >= 5,
"multi-value per_segment_values must surface at least one chunk per requested value; \
expected ≥ 5 (2+1+2), got {}",
filtered_ids.len(),
);
assert!(
filtered_ids.len() < range_baseline_ids.len(),
"multi-value per_segment_values must still strictly narrow vs the range baseline; \
range_baseline={} filtered={}",
range_baseline_ids.len(),
filtered_ids.len(),
);
let single_value_request = QueryDatasetRequest {
segment_ids: segment_ids.clone(),
select_all_entity_paths: true,
query: Some(ext::Query {
latest_at: Some(ext::QueryLatestAt {
index: Some(index_name.into()),
at: TimeInt::STATIC,
per_segment_values: vec![vec![1010], vec![2020], vec![3010]],
}),
..Default::default()
}),
..Default::default()
};
let single_value_ids =
per_segment_chunk_id_set(service, dataset_name, single_value_request).await;
assert!(
single_value_ids.len() < filtered_ids.len(),
"asking for more values per segment must surface more chunks; \
single_value={} multi_value={}",
single_value_ids.len(),
filtered_ids.len(),
);
assert!(
single_value_ids.is_subset(&filtered_ids),
"single-value result must be a subset of the multi-value result"
);
}
pub async fn query_dataset_per_segment_values_validation_rejected(service: impl RerunCloudService) {
use re_protos::cloud::v1alpha1::QueryDatasetRequest as WireQueryDatasetRequest;
use re_protos::headers::RerunHeadersInjectorExt as _;
let dataset_name = "per_segment_validation_rejected";
let _data_sources =
register_per_segment_dataset(&service, dataset_name, 99, TimeType::Sequence).await;
let request = WireQueryDatasetRequest {
segment_ids: vec![
re_protos::common::v1alpha1::SegmentId {
id: Some("seg1".to_owned()),
},
re_protos::common::v1alpha1::SegmentId {
id: Some("seg2".to_owned()),
},
re_protos::common::v1alpha1::SegmentId {
id: Some("seg3".to_owned()),
},
],
select_all_entity_paths: true,
query: Some(re_protos::cloud::v1alpha1::Query {
latest_at: Some(re_protos::cloud::v1alpha1::QueryLatestAt {
index: Some(re_protos::common::v1alpha1::IndexColumnSelector {
timeline: Some(re_protos::common::v1alpha1::Timeline {
name: "frame_nr".into(),
}),
}),
at: Some(1010),
per_segment_values: vec![
re_protos::cloud::v1alpha1::IndexValueList { values: vec![1010] },
re_protos::cloud::v1alpha1::IndexValueList { values: vec![2010] },
re_protos::cloud::v1alpha1::IndexValueList { values: vec![3010] },
],
}),
..Default::default()
}),
..Default::default()
};
let result = service
.query_dataset(
tonic::Request::new(request)
.with_entry_name(crate::tests::common::entry_name(dataset_name)),
)
.await;
match result {
Ok(_) => panic!(
"server must reject `per_segment_values` combined with `at != STATIC` \
with InvalidArgument; got Ok",
),
Err(err) => {
assert_eq!(
err.code(),
tonic::Code::InvalidArgument,
"expected InvalidArgument, got {err}",
);
}
}
}
pub async fn query_dataset_per_segment_values_with_chunk_ids_intersects(
service: impl RerunCloudService,
) {
let service = Arc::new(service);
for time_type in [
TimeType::Sequence,
TimeType::DurationNs,
TimeType::TimestampNs,
] {
query_dataset_per_segment_values_with_chunk_ids_intersects_by_time_type(
service.as_ref(),
time_type,
)
.await;
}
}
async fn query_dataset_per_segment_values_with_chunk_ids_intersects_by_time_type<
T: RerunCloudService,
>(
service: &T,
time_type: TimeType,
) {
use re_protos::cloud::v1alpha1::QueryDatasetResponse;
use re_protos::cloud::v1alpha1::ext::QueryDatasetRequest;
use re_protos::headers::RerunHeadersInjectorExt as _;
let dataset_name = &format!("per_segment_chunk_ids_intersect_{time_type}");
let tuid_prefix = match time_type {
TimeType::Sequence => 111,
TimeType::DurationNs => 112,
TimeType::TimestampNs => 113,
};
let _data_sources =
register_per_segment_dataset(service, dataset_name, tuid_prefix, time_type).await;
let index_name = re_log_types::build_index_value(0_i64, time_type)
.0
.name()
.as_str();
let segment_ids = per_segment_segment_ids();
let baseline_request = QueryDatasetRequest {
segment_ids: segment_ids.clone(),
select_all_entity_paths: true,
query: Some(ext::Query {
latest_at: Some(ext::QueryLatestAt {
index: Some(index_name.into()),
at: TimeInt::STATIC,
per_segment_values: vec![vec![1010], vec![2010], vec![3010]],
}),
..Default::default()
}),
..Default::default()
};
let baseline_ids = per_segment_chunk_id_set(service, dataset_name, baseline_request).await;
assert!(
baseline_ids.len() >= 2,
"baseline must return ≥ 2 chunks so we can pick one and meaningfully \
narrow; got {}",
baseline_ids.len(),
);
let pinned_chunk_id = *baseline_ids.iter().next().expect("baseline non-empty");
let intersected_request = QueryDatasetRequest {
segment_ids: segment_ids.clone(),
chunk_ids: vec![pinned_chunk_id],
select_all_entity_paths: true,
query: Some(ext::Query {
latest_at: Some(ext::QueryLatestAt {
index: Some(index_name.into()),
at: TimeInt::STATIC,
per_segment_values: vec![vec![1010], vec![2010], vec![3010]],
}),
..Default::default()
}),
..Default::default()
};
let request_wire: re_protos::cloud::v1alpha1::QueryDatasetRequest = intersected_request.into();
let response = service
.query_dataset(
tonic::Request::new(request_wire)
.with_entry_name(crate::tests::common::entry_name(dataset_name)),
)
.await;
let mut stream = match response {
Ok(resp) => Box::pin(resp.into_inner()),
Err(err) if err.code() == tonic::Code::Unimplemented => {
return;
}
Err(err) => panic!("query_dataset failed unexpectedly: {err}"),
};
let mut intersected_ids: BTreeSet<re_chunk::ChunkId> = BTreeSet::new();
while let Some(resp) = stream.next().await {
let resp: QueryDatasetResponse = resp.unwrap();
if let Some(part) = resp.data {
let batch: arrow::array::RecordBatch = part.try_into().unwrap();
let id_col = QueryDatasetDataframe::COLUMN_CHUNK_ID
.extract(&batch)
.expect("bad chunk_id column in response");
intersected_ids.extend(id_col.iter_owned());
}
}
assert_eq!(
intersected_ids,
BTreeSet::from([pinned_chunk_id]),
"chunk_ids ∩ per_segment_values must equal pinned_chunk_id; \
got {intersected_ids:?}",
);
}
pub async fn query_dataset_per_segment_values_empty_entity_paths_short_circuits(
service: impl RerunCloudService,
) {
let service = Arc::new(service);
for time_type in [
TimeType::Sequence,
TimeType::DurationNs,
TimeType::TimestampNs,
] {
query_dataset_per_segment_values_empty_entity_paths_short_circuits_by_time_type(
service.as_ref(),
time_type,
)
.await;
}
}
async fn query_dataset_per_segment_values_empty_entity_paths_short_circuits_by_time_type<
T: RerunCloudService,
>(
service: &T,
time_type: TimeType,
) {
use re_protos::cloud::v1alpha1::ext::QueryDatasetRequest;
let dataset_name = &format!("per_segment_empty_entity_paths_{time_type}");
let tuid_prefix = match time_type {
TimeType::Sequence => 222,
TimeType::DurationNs => 223,
TimeType::TimestampNs => 224,
};
let _data_sources =
register_per_segment_dataset(service, dataset_name, tuid_prefix, time_type).await;
let index_name = re_log_types::build_index_value(0_i64, time_type)
.0
.name()
.as_str();
let request = QueryDatasetRequest {
segment_ids: per_segment_segment_ids(),
select_all_entity_paths: false,
entity_paths: vec![],
query: Some(ext::Query {
latest_at: Some(ext::QueryLatestAt {
index: Some(index_name.into()),
at: TimeInt::STATIC,
per_segment_values: vec![vec![1010], vec![2010], vec![3010]],
}),
..Default::default()
}),
..Default::default()
};
let ids = per_segment_chunk_id_set(service, dataset_name, request).await;
assert!(
ids.is_empty(),
"(select_all_entity_paths=false, entity_paths=[]) must yield an empty \
result regardless of `per_segment_values`; got {} chunk(s)",
ids.len(),
);
}
async fn query_dataset_snapshot<T: RerunCloudService>(
client: TestClient<T>,
dataset_entry: &DatasetEntry,
index_values: Vec<(&str, Vec<i64>)>,
snapshot_name: &str,
time_type: TimeType,
check_schema: bool,
) {
let index_values: BTreeMap<SegmentId, BTreeSet<IndexValue>> = index_values
.into_iter()
.map(|(idx, values)| {
(
SegmentId::from(idx),
values.into_iter().map(TimeInt::new_temporal).collect(),
)
})
.collect();
let timeline_name = match time_type {
TimeType::Sequence => "frame_nr",
TimeType::DurationNs => "duration",
TimeType::TimestampNs => "timestamp",
};
let query = re_chunk_store::QueryExpression {
view_contents: Some(std::iter::once((EntityPath::from("my/entity"), None)).collect()),
filtered_index: Some(timeline_name.into()),
..Default::default()
};
let table_provider = DataframeQueryTableProvider::new_from_client(
client,
dataset_entry.details.id,
&query,
&[] as &[&str],
Some(Arc::new(index_values)),
None, None, Vec::new(), )
.await
.unwrap();
let ctx = SessionContext::default();
let plan = table_provider
.scan(&ctx.state(), None, &[], None)
.await
.unwrap();
let schema = plan.schema();
let num_partitions = plan.output_partitioning().partition_count();
let results: Vec<_> = (0..num_partitions)
.map(|partition| plan.execute(partition, ctx.task_ctx()))
.try_collect()
.unwrap();
let stream = futures::stream::iter(results);
let results: Vec<RecordBatch> = stream
.flat_map(|stream| stream)
.try_collect()
.await
.unwrap();
for batch in &results {
assert_eq!(batch.schema(), schema);
}
let results = if results.is_empty() {
RecordBatch::new_empty(schema)
} else {
concat_record_batches(&results)
};
if check_schema {
insta::assert_snapshot!(
format!("{snapshot_name}_schema"),
results.format_schema_snapshot()
);
}
let filtered_results = results.horizontally_sorted().auto_sort_rows().unwrap();
insta::assert_snapshot!(
format!("{snapshot_name}_data"),
filtered_results.format_snapshot(false)
);
}