boreholeio 0.1.0

A library for interacting with borehole.io, a subsurface data management, delivery and visualisation platform
Documentation
use crate::strided_array_file::{ArrayProxy, Error, StridedArrayFile, TypedArray};
use std::{fmt::Debug, sync::Arc};

/// `ArrayCache` returns arrays views whose data type is abstracted away into
/// `ArrayProxy`. An object which owns the memory `ArrayProxy` points to, must
/// implement the `Resource` trait.
pub(super) trait Resource: Debug + Send + Sync {
    fn is_parent_of(&self, proxy: ArrayProxy<'_>) -> Result<bool, Error>;
}

/// Generally speaking, every `Resource` produces multiple cache entries. For
/// example, single Strided Array File can contain multiple arrays, each of
/// which is associated with a different URI. Additionally, every array can be
/// sliced multiple times, with each slice assigned its own URI. We require our
/// cached views to implement the `View` trait, in order to delegate `Resource`
/// lifetimes to Rust. Thus, every `View` is self-sufficient because it is tied
/// to the corresponding `Arc<Resource>`.
pub(super) trait View<'a>: Debug + Send + Sync {
    fn proxy(&self) -> Result<ArrayProxy<'a>, Error>;

    fn resource(&self) -> Arc<dyn Resource + 'a>;
}

/// Adds a method for creating views to specified arrays from
/// `Arc<StridedArrayFile>` resource.
pub(super) trait FileResource<'a> {
    fn create_view(&self, array_index: usize) -> Arc<FileView<'a>>;
}

impl<'a> FileResource<'a> for Arc<StridedArrayFile<'a>> {
    fn create_view(&self, array_index: usize) -> Arc<FileView<'a>> {
        Arc::new(FileView {
            array_index,
            file: self.clone(),
        })
    }
}

/// Implementing only `View` for `StridedArrayFile` is not enough because the
/// file may contain multiple arrays - meaning that, in the future, we may need
/// to access other arrays from that same resource. Additionally, the file must
/// support "lazy" loading, which means we don't have `ArrayProxy` at the
/// moment the resource is created. This behavior is very different from that
/// of "in-memory" arrays, that's why the two types cannot be generalized by a
/// single common trait.
#[derive(Debug)]
pub(super) struct FileView<'a> {
    array_index: usize,
    file: Arc<StridedArrayFile<'a>>,
}

/// Creates new in-memory `Resource` and returns `View` to it.
pub(super) fn create_new_array<'a>(array: TypedArray) -> Result<Arc<dyn View<'a> + 'a>, Error> {
    let proxy = unsafe { std::mem::transmute::<ArrayProxy<'_>, ArrayProxy<'a>>(array.proxy()?) };
    let resource = Arc::new(array);
    Ok(Arc::new(ResourceView { proxy, resource }))
}

/// Creates a `View` to existing resource.
pub(super) fn create_view<'a>(
    proxy: ArrayProxy<'a>,
    resource: Arc<dyn Resource + 'a>,
) -> Result<Arc<dyn View<'a> + 'a>, Error> {
    if !resource.is_parent_of(proxy.clone())? {
        Err("Inconsistent caching data".into())
    } else {
        Ok(Arc::new(ResourceView { proxy, resource }))
    }
}

impl Resource for TypedArray {
    fn is_parent_of(&self, proxy: ArrayProxy<'_>) -> Result<bool, Error> {
        self.proxy()?.contains(proxy)
    }
}

/// TODO Remove lifetime specifier from `StridedArrayFile`.
impl Resource for StridedArrayFile<'_> {
    fn is_parent_of(&self, proxy: ArrayProxy<'_>) -> Result<bool, Error> {
        // Do not open the file if it's not been opened yet.
        if !self.is_open() {
            return Ok(false);
        }
        // Try every array from the file.
        for i in 0..self.count()? {
            if self.try_get(i)?.contains(proxy.clone())? {
                return Ok(true);
            }
        }
        Ok(false)
    }
}

impl<'a> View<'a> for FileView<'a> {
    fn proxy(&self) -> Result<ArrayProxy<'a>, Error> {
        self.file.try_get(self.array_index)
    }

    fn resource(&self) -> Arc<dyn Resource + 'a> {
        self.file.clone()
    }
}

#[derive(Debug)]
struct ResourceView<'a> {
    proxy: ArrayProxy<'a>,
    resource: Arc<dyn Resource + 'a>,
}

impl<'a> View<'a> for ResourceView<'a> {
    fn proxy(&self) -> Result<ArrayProxy<'a>, Error> {
        Ok(self.proxy.clone())
    }

    fn resource(&self) -> Arc<dyn Resource + 'a> {
        self.resource.clone()
    }
}

/// Tests the logic of the `Resource::is_parent_of()` method.
#[test]
fn is_parent_of() {
    use ndarray::{Array1, SliceInfoElem};
    use std::path::Path;

    // Create an in-memory array for testing.
    let array = {
        let array = Array1::from_vec(vec![1u8, 2, 3]);
        TypedArray::from_array(array.into_dyn()).unwrap()
    };
    let slice_info = vec![SliceInfoElem::Slice {
        start: 1,
        end: None,
        step: 1,
    }];
    let slice = array.slice(slice_info.clone()).unwrap();

    // We cannot compare pointers.
    assert_ne!(
        array.proxy().unwrap().try_as::<u8>().unwrap().as_ptr(),
        slice.try_as::<u8>().unwrap().as_ptr(),
    );
    // Instead, we have to compare data spans.
    assert!(array.is_parent_of(slice).unwrap());
    // Every array ia a parent to itself.
    assert!(array.is_parent_of(array.proxy().unwrap()).unwrap());
    // Resource is not a parent to any other resource's view.
    let other_array = {
        let array = Array1::from_vec(vec![1u8, 2, 3]);
        TypedArray::from_array(array.into_dyn()).unwrap()
    };
    assert!(!array.is_parent_of(other_array.proxy().unwrap()).unwrap());

    // Similarly with Strided Array Files.
    let file_path = Path::new("is_parent_of.star");
    StridedArrayFile::write(
        file_path,
        &[array.proxy().unwrap(), other_array.proxy().unwrap()],
    )
    .unwrap();
    let file = StridedArrayFile::new(file_path.to_path_buf());
    let slice = file.get(1).slice(slice_info).unwrap();
    assert!(file.is_parent_of(slice).unwrap());
    assert!(file.is_parent_of(file.get(1)).unwrap());
    assert!(!file.is_parent_of(array.proxy().unwrap()).unwrap());
}