use std::cmp::Ordering;
use std::sync::Arc;
use super::*;
use crate::executor::streaming::chunk::core::DataChunk;
use crate::executor::streaming::chunk::schema::{ColumnInfo, Schema};
use crate::executor::streaming::chunk::{EdgeHeaderColumn, TypedColumn};
use crate::executor::streaming::helpers::compare_values;
use linkrs_core::types::storage_ids::VertexId;
use linkrs_core::EdgeHeader;
use linkrs_core::Value;
fn chunk_of(rows: Vec<Vec<Value>>) -> DataChunk {
let mut chunk = DataChunk::new(
rows,
Arc::new(Schema::new(vec![ColumnInfo {
name: "val".to_string(),
data_type: "bigint".to_string(),
}])),
);
chunk.build_typed_columns(true);
chunk
}
#[test]
fn test_append_chunk_typed_i64() {
let mut batch = ColumnarBatch::new(1);
let chunk = chunk_of(vec![
vec![Value::BigInt(1)],
vec![Value::BigInt(2)],
vec![Value::BigInt(3)],
]);
batch.append_chunk(&chunk);
assert_eq!(batch.num_rows(), 3);
assert!(batch.column(0).is_typed());
assert_eq!(batch.column(0).value_at(1), Value::BigInt(2));
assert_eq!(batch.column(0).compare_at(0, 2), Ordering::Less);
assert_eq!(
batch.to_rows(),
vec![
vec![Value::BigInt(1)],
vec![Value::BigInt(2)],
vec![Value::BigInt(3)],
]
);
}
#[test]
fn test_append_chunk_degrades_on_kind_mismatch() {
let mut batch = ColumnarBatch::new(1);
let c1 = chunk_of(vec![vec![Value::BigInt(1)], vec![Value::BigInt(2)]]);
batch.append_chunk(&c1);
assert!(batch.column(0).is_typed());
let mut c2 = DataChunk::new(
vec![vec![Value::Int(7)], vec![Value::Int(8)]],
Arc::new(Schema::new(vec![ColumnInfo {
name: "val".to_string(),
data_type: "int".to_string(),
}])),
);
c2.build_typed_columns(true);
batch.append_chunk(&c2);
assert!(!batch.column(0).is_typed());
assert_eq!(batch.num_rows(), 4);
let rows = batch.to_rows();
assert_eq!(rows[0][0], Value::BigInt(1));
assert_eq!(rows[2][0], Value::Int(7));
assert_eq!(rows[3][0], Value::Int(8));
}
#[test]
fn test_permute_and_truncate() {
let mut batch = ColumnarBatch::new(1);
batch.append_chunk(&chunk_of(vec![
vec![Value::BigInt(3)],
vec![Value::BigInt(1)],
vec![Value::BigInt(2)],
]));
batch.permute(&[1, 2, 0]);
assert_eq!(batch.column(0).value_at(0), Value::BigInt(1));
assert_eq!(batch.column(0).value_at(1), Value::BigInt(2));
assert_eq!(batch.column(0).value_at(2), Value::BigInt(3));
batch.truncate(2);
assert_eq!(batch.num_rows(), 2);
assert_eq!(batch.to_rows()[1][0], Value::BigInt(2));
}
#[test]
fn test_utf8_column_ordering() {
let mut batch = ColumnarBatch::new(1);
batch.append_chunk(&chunk_of(vec![
vec![Value::String("pear".into())],
vec![Value::String("apple".into())],
vec![Value::String("fig".into())],
]));
assert!(batch.column(0).is_typed());
assert_eq!(
batch.column(0).compare_at(1, 0),
Ordering::Less,
"apple < pear"
);
assert_eq!(
batch.column(0).compare_at(2, 0),
Ordering::Less,
"fig < pear"
);
}
#[test]
fn test_datetime_and_decimal_columns() {
use linkrs_core::value::date_time::DateTimeValue;
use linkrs_core::value::decimal128::Decimal128Value;
let dt = |day: u32| {
Value::DateTime(DateTimeValue {
year: 2024,
month: 1,
day,
hour: 0,
minute: 0,
sec: 0,
microsec: 0,
})
};
let mut batch = ColumnarBatch::new(2);
let mut chunk = DataChunk::new(
vec![
vec![dt(3), Value::Decimal128(Decimal128Value::from_i64(30))],
vec![dt(1), Value::Decimal128(Decimal128Value::from_i64(10))],
vec![dt(2), Value::Decimal128(Decimal128Value::from_i64(20))],
],
Arc::new(Schema::new(vec![
ColumnInfo {
name: "dt".to_string(),
data_type: "datetime".to_string(),
},
ColumnInfo {
name: "dec".to_string(),
data_type: "decimal128".to_string(),
},
])),
);
chunk.build_typed_columns(true);
batch.append_chunk(&chunk);
assert!(batch.column(0).is_typed());
assert!(batch.column(1).is_typed());
assert_eq!(
batch.column(0).compare_at(0, 1),
Ordering::Greater,
"row 0 (day 3) > row 1 (day 1)"
);
assert_eq!(
batch.column(1).compare_at(0, 2),
Ordering::Greater,
"30 > 20"
);
assert_eq!(
batch.column(0).value_at(1),
dt(1),
"DateTime materializes from micros"
);
assert_eq!(
batch.column(1).value_at(2),
Value::Decimal128(Decimal128Value::from_i64(20))
);
}
#[test]
fn test_compare_value_at_mixed_kind_falls_back() {
let mut batch = ColumnarBatch::new(1);
batch.append_chunk(&chunk_of(vec![vec![Value::BigInt(100)]]));
assert_eq!(
batch.compare_value_at(0, &Value::Int(5), 0),
compare_values(&Value::Int(5), &Value::BigInt(100))
);
assert_eq!(
batch.compare_value_at(0, &Value::BigInt(50), 0),
Ordering::Less
);
}
#[test]
fn test_estimated_size() {
let mut batch = ColumnarBatch::new(1);
let chunk = chunk_of(vec![
vec![Value::BigInt(1)],
vec![Value::BigInt(2)],
vec![Value::BigInt(3)],
]);
batch.append_chunk(&chunk);
assert!(batch.estimated_size() >= 3 * std::mem::size_of::<i64>());
}
fn vid(i: i64) -> VertexId {
VertexId::try_from_int64(i).expect("valid vertex id")
}
fn identity_chunk(ids: Vec<VertexId>) -> DataChunk {
let rows: Vec<Vec<Value>> = ids.iter().map(|&id| vec![Value::VertexId(id)]).collect();
let mut chunk = DataChunk::new(
rows,
Arc::new(Schema::new(vec![ColumnInfo {
name: "v".to_string(),
data_type: "vertex".to_string(),
}])),
);
chunk.typed_columns = Some(vec![TypedColumn::VertexIdentity(ids)]);
chunk
}
fn header_chunk() -> DataChunk {
let mut chunk = DataChunk::new(
vec![
vec![Value::edge_header(EdgeHeader::new(
vid(1),
vid(2),
"rated".to_string(),
0,
))],
vec![Value::edge_header(EdgeHeader::new(
vid(2),
vid(3),
"knows".to_string(),
1,
))],
],
Arc::new(Schema::new(vec![ColumnInfo {
name: "e".to_string(),
data_type: "edge".to_string(),
}])),
);
chunk.typed_columns = Some(vec![TypedColumn::EdgeHeader(EdgeHeaderColumn::from_parts(
vec![vid(1), vid(2)],
vec![vid(2), vid(3)],
vec!["rated".to_string(), "knows".to_string()],
vec![0, 1],
))]);
chunk
}
#[test]
fn test_identity_columns_accumulate_typed() {
let mut batch = ColumnarBatch::new(1);
batch.append_chunk(&identity_chunk(vec![vid(2), vid(1)]));
assert_eq!(batch.num_rows(), 2);
assert!(batch.column(0).is_typed());
assert_eq!(batch.column(0).value_at(0), Value::VertexId(vid(2)));
assert_eq!(batch.column(0).compare_at(0, 1), Ordering::Greater);
batch.append_chunk(&identity_chunk(vec![vid(3)]));
assert_eq!(batch.num_rows(), 3);
assert!(batch.column(0).is_typed());
assert_eq!(
batch.to_rows(),
vec![
vec![Value::VertexId(vid(2))],
vec![Value::VertexId(vid(1))],
vec![Value::VertexId(vid(3))],
]
);
batch.truncate(2);
assert_eq!(batch.num_rows(), 2);
batch.permute(&[1, 0]);
assert_eq!(
batch.to_rows(),
vec![vec![Value::VertexId(vid(1))], vec![Value::VertexId(vid(2))]]
);
}
#[test]
fn test_edge_header_column_accumulates_typed() {
let mut batch = ColumnarBatch::new(1);
batch.append_chunk(&header_chunk());
assert_eq!(batch.num_rows(), 2);
assert!(batch.column(0).is_typed());
assert_eq!(
batch.column(0).value_at(1),
Value::edge_header(EdgeHeader::new(vid(2), vid(3), "knows".to_string(), 1))
);
assert_eq!(batch.column(0).compare_at(1, 1), Ordering::Equal);
assert_eq!(batch.to_rows()[0][0], batch.column(0).value_at(0));
}
#[test]
fn test_identity_column_degrades_on_kind_mismatch() {
let mut batch = ColumnarBatch::new(1);
batch.append_chunk(&identity_chunk(vec![vid(1), vid(2)]));
assert!(batch.column(0).is_typed());
batch.append_chunk(&header_chunk());
assert!(!batch.column(0).is_typed());
assert_eq!(batch.num_rows(), 4);
let rows = batch.to_rows();
assert_eq!(rows[0][0], Value::VertexId(vid(1)));
assert_eq!(
rows[2][0],
Value::edge_header(EdgeHeader::new(vid(1), vid(2), "rated".to_string(), 0))
);
}
#[test]
fn test_identity_column_row_appends() {
let mut batch = ColumnarBatch::new(1);
batch.append_chunk(&identity_chunk(vec![vid(1)]));
batch.append_row(&[Value::VertexId(vid(9))]);
assert!(batch.column(0).is_typed());
assert_eq!(batch.num_rows(), 2);
batch.append_row(&[Value::Int(0)]);
assert!(!batch.column(0).is_typed());
assert_eq!(batch.num_rows(), 3);
assert_eq!(batch.to_rows()[2][0], Value::Int(0));
}