use linkrs_core::types::DataType;
#[derive(Debug, Clone, PartialEq)]
pub enum ColumnValues {
I64 { values: Vec<i64>, valid: Vec<u8> },
F64 { values: Vec<f64>, valid: Vec<u8> },
I32 { values: Vec<i32>, valid: Vec<u8> },
Bool { values: Vec<u8>, valid: Vec<u8> },
I16 { values: Vec<i16>, valid: Vec<u8> },
F32 { values: Vec<f32>, valid: Vec<u8> },
General(Vec<Option<linkrs_core::Value>>),
}
impl ColumnValues {
pub fn len(&self) -> usize {
match self {
ColumnValues::I64 { values, .. } => values.len(),
ColumnValues::F64 { values, .. } => values.len(),
ColumnValues::I32 { values, .. } => values.len(),
ColumnValues::Bool { values, .. } => values.len(),
ColumnValues::I16 { values, .. } => values.len(),
ColumnValues::F32 { values, .. } => values.len(),
ColumnValues::General(values) => values.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn value_at(&self, idx: usize) -> Option<linkrs_core::Value> {
match self {
ColumnValues::I64 { values, valid } => {
if valid.get(idx).copied().unwrap_or(0) == 1 {
values.get(idx).map(|&v| linkrs_core::Value::BigInt(v))
} else {
None
}
}
ColumnValues::F64 { values, valid } => {
if valid.get(idx).copied().unwrap_or(0) == 1 {
values.get(idx).map(|&v| linkrs_core::Value::Double(v))
} else {
None
}
}
ColumnValues::I32 { values, valid } => {
if valid.get(idx).copied().unwrap_or(0) == 1 {
values.get(idx).map(|&v| linkrs_core::Value::Int(v))
} else {
None
}
}
ColumnValues::Bool { values, valid } => {
if valid.get(idx).copied().unwrap_or(0) == 1 {
values.get(idx).map(|&v| linkrs_core::Value::Bool(v != 0))
} else {
None
}
}
ColumnValues::I16 { values, valid } => {
if valid.get(idx).copied().unwrap_or(0) == 1 {
values.get(idx).map(|&v| linkrs_core::Value::SmallInt(v))
} else {
None
}
}
ColumnValues::F32 { values, valid } => {
if valid.get(idx).copied().unwrap_or(0) == 1 {
values.get(idx).map(|&v| linkrs_core::Value::Float(v))
} else {
None
}
}
ColumnValues::General(values) => values.get(idx).cloned().flatten(),
}
}
pub fn append(&mut self, other: ColumnValues) {
if matches!(self, ColumnValues::General(values) if values.is_empty()) {
*self = other;
return;
}
match (self, other) {
(
ColumnValues::I64 { values, valid },
ColumnValues::I64 {
values: v2,
valid: v2v,
},
) => {
values.extend(v2);
valid.extend(v2v);
}
(
ColumnValues::F64 { values, valid },
ColumnValues::F64 {
values: v2,
valid: v2v,
},
) => {
values.extend(v2);
valid.extend(v2v);
}
(
ColumnValues::I32 { values, valid },
ColumnValues::I32 {
values: v2,
valid: v2v,
},
) => {
values.extend(v2);
valid.extend(v2v);
}
(
ColumnValues::Bool { values, valid },
ColumnValues::Bool {
values: v2,
valid: v2v,
},
) => {
values.extend(v2);
valid.extend(v2v);
}
(
ColumnValues::I16 { values, valid },
ColumnValues::I16 {
values: v2,
valid: v2v,
},
) => {
values.extend(v2);
valid.extend(v2v);
}
(
ColumnValues::F32 { values, valid },
ColumnValues::F32 {
values: v2,
valid: v2v,
},
) => {
values.extend(v2);
valid.extend(v2v);
}
(ColumnValues::General(values), ColumnValues::General(values2)) => {
values.extend(values2);
}
(self_col, other) => {
let mut general = self_col.to_general();
general.extend(other.to_general());
*self_col = ColumnValues::General(general);
}
}
}
pub fn append_nulls(&mut self, n: usize) {
match self {
ColumnValues::I64 { values, valid } => {
values.resize(values.len() + n, 0);
valid.resize(valid.len() + n, 0);
}
ColumnValues::F64 { values, valid } => {
values.resize(values.len() + n, 0.0);
valid.resize(valid.len() + n, 0);
}
ColumnValues::I32 { values, valid } => {
values.resize(values.len() + n, 0);
valid.resize(valid.len() + n, 0);
}
ColumnValues::Bool { values, valid } => {
values.resize(values.len() + n, 0);
valid.resize(valid.len() + n, 0);
}
ColumnValues::I16 { values, valid } => {
values.resize(values.len() + n, 0);
valid.resize(valid.len() + n, 0);
}
ColumnValues::F32 { values, valid } => {
values.resize(values.len() + n, 0.0);
valid.resize(valid.len() + n, 0);
}
ColumnValues::General(values) => {
values.resize(values.len() + n, None);
}
}
}
pub fn truncate(&mut self, n: usize) {
match self {
ColumnValues::I64 { values, valid } => {
values.truncate(n);
valid.truncate(n);
}
ColumnValues::F64 { values, valid } => {
values.truncate(n);
valid.truncate(n);
}
ColumnValues::I32 { values, valid } => {
values.truncate(n);
valid.truncate(n);
}
ColumnValues::Bool { values, valid } => {
values.truncate(n);
valid.truncate(n);
}
ColumnValues::I16 { values, valid } => {
values.truncate(n);
valid.truncate(n);
}
ColumnValues::F32 { values, valid } => {
values.truncate(n);
valid.truncate(n);
}
ColumnValues::General(values) => values.truncate(n),
}
}
pub fn compact(&mut self, keep: &[bool]) {
let selection: Vec<usize> = keep
.iter()
.enumerate()
.filter_map(|(i, &k)| if k { Some(i) } else { None })
.collect();
self.select(&selection);
}
pub fn select(&mut self, selection: &[usize]) {
match self {
ColumnValues::I64 { values, valid } => {
let mut next_values = Vec::with_capacity(selection.len());
let mut next_valid = Vec::with_capacity(selection.len());
for &i in selection {
next_values.push(values[i]);
next_valid.push(valid[i]);
}
*values = next_values;
*valid = next_valid;
}
ColumnValues::F64 { values, valid } => {
let mut next_values = Vec::with_capacity(selection.len());
let mut next_valid = Vec::with_capacity(selection.len());
for &i in selection {
next_values.push(values[i]);
next_valid.push(valid[i]);
}
*values = next_values;
*valid = next_valid;
}
ColumnValues::I32 { values, valid } => {
let mut next_values = Vec::with_capacity(selection.len());
let mut next_valid = Vec::with_capacity(selection.len());
for &i in selection {
next_values.push(values[i]);
next_valid.push(valid[i]);
}
*values = next_values;
*valid = next_valid;
}
ColumnValues::Bool { values, valid } => {
let mut next_values = Vec::with_capacity(selection.len());
let mut next_valid = Vec::with_capacity(selection.len());
for &i in selection {
next_values.push(values[i]);
next_valid.push(valid[i]);
}
*values = next_values;
*valid = next_valid;
}
ColumnValues::I16 { values, valid } => {
let mut next_values = Vec::with_capacity(selection.len());
let mut next_valid = Vec::with_capacity(selection.len());
for &i in selection {
next_values.push(values[i]);
next_valid.push(valid[i]);
}
*values = next_values;
*valid = next_valid;
}
ColumnValues::F32 { values, valid } => {
let mut next_values = Vec::with_capacity(selection.len());
let mut next_valid = Vec::with_capacity(selection.len());
for &i in selection {
next_values.push(values[i]);
next_valid.push(valid[i]);
}
*values = next_values;
*valid = next_valid;
}
ColumnValues::General(values) => {
let mut next = Vec::with_capacity(selection.len());
for &i in selection {
next.push(values[i].take());
}
*values = next;
}
}
}
pub fn to_general(&self) -> Vec<Option<linkrs_core::Value>> {
(0..self.len()).map(|i| self.value_at(i)).collect()
}
pub fn scatter(&self, target: &mut ColumnValues, positions: &[(usize, u32)]) {
for (i, &(out_idx, _)) in positions.iter().enumerate() {
let value = self.value_at(i);
if !target.set_value_at(out_idx, value.clone()) {
let mut general = target.to_general();
if out_idx < general.len() {
general[out_idx] = value;
}
*target = ColumnValues::General(general);
}
}
}
fn set_value_at(&mut self, idx: usize, value: Option<linkrs_core::Value>) -> bool {
match self {
ColumnValues::I64 { values, valid } => match value {
Some(linkrs_core::Value::BigInt(v)) => {
if idx < values.len() {
values[idx] = v;
valid[idx] = 1;
true
} else {
false
}
}
None => {
if idx < valid.len() {
valid[idx] = 0;
true
} else {
false
}
}
Some(_) => false,
},
ColumnValues::F64 { values, valid } => match value {
Some(linkrs_core::Value::Double(v)) => {
if idx < values.len() {
values[idx] = v;
valid[idx] = 1;
true
} else {
false
}
}
None => {
if idx < valid.len() {
valid[idx] = 0;
true
} else {
false
}
}
Some(_) => false,
},
ColumnValues::I32 { values, valid } => match value {
Some(linkrs_core::Value::Int(v)) => {
if idx < values.len() {
values[idx] = v;
valid[idx] = 1;
true
} else {
false
}
}
None => {
if idx < valid.len() {
valid[idx] = 0;
true
} else {
false
}
}
Some(_) => false,
},
ColumnValues::Bool { values, valid } => match value {
Some(linkrs_core::Value::Bool(v)) => {
if idx < values.len() {
values[idx] = u8::from(v);
valid[idx] = 1;
true
} else {
false
}
}
None => {
if idx < valid.len() {
valid[idx] = 0;
true
} else {
false
}
}
Some(_) => false,
},
ColumnValues::I16 { values, valid } => match value {
Some(linkrs_core::Value::SmallInt(v)) => {
if idx < values.len() {
values[idx] = v;
valid[idx] = 1;
true
} else {
false
}
}
None => {
if idx < valid.len() {
valid[idx] = 0;
true
} else {
false
}
}
Some(_) => false,
},
ColumnValues::F32 { values, valid } => match value {
Some(linkrs_core::Value::Float(v)) => {
if idx < values.len() {
values[idx] = v;
valid[idx] = 1;
true
} else {
false
}
}
None => {
if idx < valid.len() {
valid[idx] = 0;
true
} else {
false
}
}
Some(_) => false,
},
ColumnValues::General(target_rows) => {
if idx < target_rows.len() {
target_rows[idx] = value;
true
} else {
false
}
}
}
}
pub fn from_general_with_type(
values: Vec<Option<linkrs_core::Value>>,
data_type: &DataType,
) -> Option<ColumnValues> {
match data_type {
DataType::BigInt => {
let mut vs = Vec::with_capacity(values.len());
let mut valid = vec![0u8; values.len()];
for (i, value) in values.into_iter().enumerate() {
match value {
Some(linkrs_core::Value::BigInt(v)) => {
vs.push(v);
valid[i] = 1;
}
None => vs.push(0),
Some(_) => return None,
}
}
Some(ColumnValues::I64 { values: vs, valid })
}
DataType::Double => {
let mut vs = Vec::with_capacity(values.len());
let mut valid = vec![0u8; values.len()];
for (i, value) in values.into_iter().enumerate() {
match value {
Some(linkrs_core::Value::Double(v)) => {
vs.push(v);
valid[i] = 1;
}
None => vs.push(0.0),
Some(_) => return None,
}
}
Some(ColumnValues::F64 { values: vs, valid })
}
DataType::Int => {
let mut vs = Vec::with_capacity(values.len());
let mut valid = vec![0u8; values.len()];
for (i, value) in values.into_iter().enumerate() {
match value {
Some(linkrs_core::Value::Int(v)) => {
vs.push(v);
valid[i] = 1;
}
None => vs.push(0),
Some(_) => return None,
}
}
Some(ColumnValues::I32 { values: vs, valid })
}
DataType::Bool => {
let mut vs = Vec::with_capacity(values.len());
let mut valid = vec![0u8; values.len()];
for (i, value) in values.into_iter().enumerate() {
match value {
Some(linkrs_core::Value::Bool(v)) => {
vs.push(u8::from(v));
valid[i] = 1;
}
None => vs.push(0),
Some(_) => return None,
}
}
Some(ColumnValues::Bool { values: vs, valid })
}
DataType::SmallInt => {
let mut vs = Vec::with_capacity(values.len());
let mut valid = vec![0u8; values.len()];
for (i, value) in values.into_iter().enumerate() {
match value {
Some(linkrs_core::Value::SmallInt(v)) => {
vs.push(v);
valid[i] = 1;
}
None => vs.push(0),
Some(_) => return None,
}
}
Some(ColumnValues::I16 { values: vs, valid })
}
DataType::Float => {
let mut vs = Vec::with_capacity(values.len());
let mut valid = vec![0u8; values.len()];
for (i, value) in values.into_iter().enumerate() {
match value {
Some(linkrs_core::Value::Float(v)) => {
vs.push(v);
valid[i] = 1;
}
None => vs.push(0.0),
Some(_) => return None,
}
}
Some(ColumnValues::F32 { values: vs, valid })
}
_ => None,
}
}
pub fn all_valid(&self) -> bool {
match self {
ColumnValues::I64 { valid, .. }
| ColumnValues::F64 { valid, .. }
| ColumnValues::I32 { valid, .. }
| ColumnValues::Bool { valid, .. }
| ColumnValues::I16 { valid, .. }
| ColumnValues::F32 { valid, .. } => valid.iter().all(|&v| v == 1),
ColumnValues::General(values) => values.iter().all(|v| v.is_some()),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PropertyColumn {
pub name: std::sync::Arc<str>,
pub data_type: DataType,
pub values: ColumnValues,
}
#[derive(Debug, Clone, PartialEq)]
pub struct VertexColumnBatch {
pub vids: Vec<linkrs_core::types::VertexId>,
pub internal_ids: Vec<i64>,
pub tag_names: Vec<String>,
pub columns: Vec<PropertyColumn>,
}
impl VertexColumnBatch {
pub fn empty() -> Self {
Self {
vids: Vec::new(),
internal_ids: Vec::new(),
tag_names: Vec::new(),
columns: Vec::new(),
}
}
pub fn is_empty(&self) -> bool {
self.vids.is_empty()
}
pub fn len(&self) -> usize {
self.vids.len()
}
pub fn select(&mut self, selection: &[usize]) {
let gather_ids = selection.iter().map(|&i| self.vids[i]).collect::<Vec<_>>();
let gather_internal = selection
.iter()
.map(|&i| self.internal_ids[i])
.collect::<Vec<_>>();
let gather_tags = selection
.iter()
.map(|&i| self.tag_names[i].clone())
.collect::<Vec<_>>();
self.vids = gather_ids;
self.internal_ids = gather_internal;
self.tag_names = gather_tags;
for column in &mut self.columns {
column.values.select(selection);
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EdgeColumnBatch {
pub srcs: Vec<linkrs_core::types::VertexId>,
pub dsts: Vec<linkrs_core::types::VertexId>,
pub edge_types: Vec<String>,
pub rankings: Vec<i64>,
pub columns: Vec<PropertyColumn>,
}
impl EdgeColumnBatch {
pub fn empty() -> Self {
Self {
srcs: Vec::new(),
dsts: Vec::new(),
edge_types: Vec::new(),
rankings: Vec::new(),
columns: Vec::new(),
}
}
pub fn is_empty(&self) -> bool {
self.srcs.is_empty()
}
pub fn len(&self) -> usize {
self.srcs.len()
}
pub fn select(&mut self, selection: &[usize]) {
let gather_srcs = selection.iter().map(|&i| self.srcs[i]).collect::<Vec<_>>();
let gather_dsts = selection.iter().map(|&i| self.dsts[i]).collect::<Vec<_>>();
let gather_types = selection
.iter()
.map(|&i| self.edge_types[i].clone())
.collect::<Vec<_>>();
let gather_ranks = selection
.iter()
.map(|&i| self.rankings[i])
.collect::<Vec<_>>();
self.srcs = gather_srcs;
self.dsts = gather_dsts;
self.edge_types = gather_types;
self.rankings = gather_ranks;
for column in &mut self.columns {
column.values.select(selection);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_typed_kinds_roundtrip_values() {
let b = ColumnValues::Bool {
values: vec![1, 0, 1],
valid: vec![1, 1, 0],
};
assert_eq!(b.value_at(0), Some(linkrs_core::Value::Bool(true)));
assert_eq!(b.value_at(1), Some(linkrs_core::Value::Bool(false)));
assert_eq!(b.value_at(2), None);
let mut with_null = b.clone();
with_null.set_value_at(0, None);
assert_eq!(with_null.value_at(0), None);
assert!(!with_null.all_valid());
let s = ColumnValues::I16 {
values: vec![7, -3],
valid: vec![1, 1],
};
assert_eq!(s.value_at(0), Some(linkrs_core::Value::SmallInt(7)));
assert_eq!(s.value_at(1), Some(linkrs_core::Value::SmallInt(-3)));
let f = ColumnValues::F32 {
values: vec![1.5, 0.0],
valid: vec![1, 0],
};
assert_eq!(f.value_at(0), Some(linkrs_core::Value::Float(1.5)));
assert_eq!(f.value_at(1), None);
}
#[test]
fn from_general_with_type_covers_new_kinds() {
let bools = vec![
Some(linkrs_core::Value::Bool(true)),
None,
Some(linkrs_core::Value::Bool(false)),
];
let typed = ColumnValues::from_general_with_type(bools, &DataType::Bool).unwrap();
assert!(matches!(typed, ColumnValues::Bool { .. }));
assert_eq!(typed.len(), 3);
let smalls = vec![Some(linkrs_core::Value::SmallInt(2)), None];
let typed = ColumnValues::from_general_with_type(smalls, &DataType::SmallInt).unwrap();
assert!(matches!(typed, ColumnValues::I16 { .. }));
let floats = vec![Some(linkrs_core::Value::Float(2.5)), None];
let typed = ColumnValues::from_general_with_type(floats, &DataType::Float).unwrap();
assert!(matches!(typed, ColumnValues::F32 { .. }));
let mixed = vec![Some(linkrs_core::Value::Int(1))];
assert!(ColumnValues::from_general_with_type(mixed, &DataType::Bool).is_none());
}
#[test]
fn scatter_into_typed_target_writes_directly() {
let src = ColumnValues::I16 {
values: vec![9, 8],
valid: vec![1, 1],
};
let mut target = ColumnValues::I16 {
values: vec![0; 4],
valid: vec![0; 4],
};
src.scatter(&mut target, &[(1, 0), (3, 0)]);
assert_eq!(target.value_at(0), None);
assert_eq!(target.value_at(1), Some(linkrs_core::Value::SmallInt(9)));
assert_eq!(target.value_at(2), None);
assert_eq!(target.value_at(3), Some(linkrs_core::Value::SmallInt(8)));
}
#[test]
fn scatter_kind_mismatch_degrades_to_general() {
let src = ColumnValues::I16 {
values: vec![9],
valid: vec![1],
};
let mut target = ColumnValues::I32 {
values: vec![0; 2],
valid: vec![0; 2],
};
src.scatter(&mut target, &[(0, 0)]);
assert!(matches!(target, ColumnValues::General(_)));
assert_eq!(target.value_at(0), Some(linkrs_core::Value::SmallInt(9)));
assert_eq!(target.value_at(1), None);
}
#[test]
fn append_and_compact_cover_new_kinds() {
let mut a = ColumnValues::Bool {
values: vec![1],
valid: vec![1],
};
a.append(ColumnValues::Bool {
values: vec![0],
valid: vec![0],
});
assert_eq!(a.len(), 2);
assert_eq!(a.value_at(1), None);
let mut f = ColumnValues::F32 {
values: vec![1.0, 2.0, 3.0],
valid: vec![1, 1, 1],
};
f.compact(&[true, false, true]);
assert_eq!(f.len(), 2);
assert_eq!(f.value_at(1), Some(linkrs_core::Value::Float(3.0)));
let mut s = ColumnValues::I16 {
values: vec![1],
valid: vec![1],
};
s.append_nulls(2);
assert_eq!(s.len(), 3);
assert_eq!(s.value_at(2), None);
s.truncate(1);
assert_eq!(s.len(), 1);
}
}