#![allow(dead_code, reason = "each test binary uses a different subset")]
#![allow(clippy::unwrap_used, reason = "test helpers")]
use std::collections::HashMap;
use std::sync::Arc;
use arrow::array::{Array, Float64Array, Int32Array, Int64Array, RecordBatch, StringArray, TimestampMicrosecondArray};
use arrow::datatypes::{DataType, Field, Schema, TimeUnit};
use timelake::datafusion::datasource::memory::MemorySourceConfig;
use timelake::datafusion::datasource::physical_plan::{FileGroup, FileScanConfig};
use timelake::datafusion::datasource::source::DataSourceExec;
use timelake::datafusion::physical_plan::ExecutionPlan;
use timelake::datafusion::physical_plan::empty::EmptyExec;
use timelake::{Lake, Session, TableOptions};
pub(crate) const DAY: i64 = 86_400_000_000;
pub(crate) const T0: i64 = 1_767_225_600_000_000;
pub(crate) fn schema() -> Arc<Schema> {
Arc::new(Schema::new(vec![
Field::new("time", DataType::Timestamp(TimeUnit::Microsecond, Some("UTC".into())), false),
Field::new("device", DataType::Int32, true),
Field::new("site", DataType::Utf8, true),
Field::new("value", DataType::Float64, true),
]))
}
pub(crate) fn batch(start: i64, n: i64, step: i64, devices: i32, value: impl Fn(i32, i64) -> f64) -> RecordBatch {
let points: Vec<(i64, i32)> = (0..n).flat_map(|i| (0..devices).map(move |d| (i, d))).collect();
let times = points.iter().map(|&(i, _)| start + i * step);
RecordBatch::try_new(
schema(),
vec![
Arc::new(TimestampMicrosecondArray::from_iter_values(times).with_timezone("UTC")),
Arc::new(Int32Array::from_iter_values(points.iter().map(|&(_, d)| d))),
Arc::new(StringArray::from_iter_values(points.iter().map(|&(_, d)| format!("site-{}", d % 3)))),
Arc::new(Float64Array::from_iter_values(points.iter().map(|&(i, d)| value(d, i)))),
],
)
.unwrap()
}
pub(crate) fn opts() -> TableOptions {
TableOptions { time_column: Some("time".into()), series_key: vec!["device".into()], ..Default::default() }
}
pub(crate) fn temp_lake() -> (tempfile::TempDir, Lake) {
let dir = tempfile::tempdir().unwrap();
let lake = Lake::open(dir.path().to_str().unwrap(), HashMap::new()).unwrap();
(dir, lake)
}
pub(crate) async fn rows(s: &Session, sql: &str) -> Vec<RecordBatch> {
s.sql(sql).await.unwrap().collect().await.unwrap()
}
pub(crate) async fn scalar_i64(s: &Session, sql: &str) -> i64 {
let b = rows(s, sql).await;
let c = arrow::compute::cast(b[0].column(0), &DataType::Int64).unwrap();
c.as_any().downcast_ref::<Int64Array>().unwrap().value(0)
}
pub(crate) fn strings(batches: &[RecordBatch], column: &str) -> Vec<String> {
batches
.iter()
.flat_map(|b| {
let c = arrow::compute::cast(b.column_by_name(column).unwrap(), &DataType::Utf8).unwrap();
let c = c.as_any().downcast_ref::<StringArray>().unwrap().clone();
(0..c.len()).map(move |i| c.value(i).to_string())
})
.collect()
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Scan {
LastValueIndex,
Files(usize),
Nothing,
}
#[derive(Debug)]
pub(crate) struct Plan {
pub(crate) scans: Vec<Scan>,
pub(crate) operators: Vec<String>,
}
impl Plan {
pub(crate) fn has(&self, operator: &str) -> bool {
self.operators.iter().any(|o| o == operator)
}
}
pub(crate) async fn plan(s: &Session, sql: &str) -> Plan {
fn walk(node: &Arc<dyn ExecutionPlan>, plan: &mut Plan) {
plan.operators.push(node.name().to_string());
if node.downcast_ref::<EmptyExec>().is_some() {
plan.scans.push(Scan::Nothing);
}
if let Some(exec) = node.downcast_ref::<DataSourceExec>() {
let source = exec.data_source();
if let Some(files) = source.downcast_ref::<FileScanConfig>() {
plan.scans.push(Scan::Files(files.file_groups.iter().map(FileGroup::len).sum()));
} else if source.downcast_ref::<MemorySourceConfig>().is_some() {
plan.scans.push(Scan::LastValueIndex);
}
}
for child in node.children() {
walk(child, plan);
}
}
let physical = s.sql(sql).await.unwrap().create_physical_plan().await.unwrap();
let mut plan = Plan { scans: vec![], operators: vec![] };
walk(&physical, &mut plan);
plan
}
pub(crate) fn f64_values(batches: &[RecordBatch]) -> Vec<f64> {
batches
.iter()
.flat_map(|b| {
let c = b.column_by_name("value").unwrap_or_else(|| b.column(0));
c.as_any().downcast_ref::<Float64Array>().unwrap().values().to_vec()
})
.collect()
}