Skip to main content

graphrecords_query/operations/string_operations/
slice.rs

1use super::{string_rebuild_map_bare, string_rebuild_map_indexed};
2use crate::{
3    Bare, BareValueDomain, Explain, Failure, IndexDomain, Indexed, Labeled, Operand, QueryResult,
4    capabilities::StringValue,
5    element::Preserving,
6    error::string::InvalidStringSlice,
7    execution::EvaluationCache,
8    operations::{Apply, ElementKernel, ElementPipeline, Operation, OperationContext, Prepare},
9    optimizer::{Estimate, OperationInputs, OptimizerHints, PlanIdentity, PlanInputs, Stats},
10    registry::operation_manifest,
11    traits::Slice,
12};
13use graphrecords_core::GraphRecord;
14
15fn slice_string(label: &'static str, value: &str, start: usize, end: usize) -> QueryResult<String> {
16    let characters: Vec<_> = value.chars().collect();
17
18    if start > end || end > characters.len() {
19        return Err(Failure::new(
20            label,
21            InvalidStringSlice::new(start, end, characters.len()),
22        ));
23    }
24
25    Ok(characters[start..end].iter().collect())
26}
27
28#[derive(Clone, Explain, Operation, OperationInputs, OptimizerHints, PlanIdentity, PlanInputs)]
29#[operation(scope = Element)]
30#[explain(label = "Slice")]
31#[plan(optimizer_hints(allows_limit_pushdown, empty = if_any))]
32pub struct SliceOperation {
33    #[explain(label)]
34    start: usize,
35    #[explain(label)]
36    end: usize,
37}
38
39impl Prepare for SliceOperation {
40    type Prepared<'a> = (usize, usize);
41
42    fn prepare<'a>(
43        &'a self,
44        _graphrecord: &'a GraphRecord,
45        _cache: &'a EvaluationCache<'a>,
46    ) -> QueryResult<Self::Prepared<'a>> {
47        Ok((self.start, self.end))
48    }
49}
50
51impl<I, V> ElementKernel<Indexed<I, V>> for SliceOperation
52where
53    I: IndexDomain,
54    V: StringValue,
55{
56    type Emission = Preserving;
57    type OutShape = Indexed<I, V>;
58
59    fn pipeline<'a>(
60        _graphrecord: &'a GraphRecord,
61        prepared: Self::Prepared<'a>,
62    ) -> QueryResult<ElementPipeline<'a, Indexed<I, V>, Self>> {
63        Ok(string_rebuild_map_indexed::<I, V>(
64            Self::LABEL,
65            move |label, value| slice_string(label, &value, prepared.0, prepared.1),
66        ))
67    }
68
69    fn estimate(&self, input: Estimate, _stats: &Stats) -> Estimate {
70        input.with_unknown_distinct()
71    }
72}
73
74impl<V> ElementKernel<Bare<V>> for SliceOperation
75where
76    V: StringValue + BareValueDomain,
77{
78    type Emission = Preserving;
79    type OutShape = Bare<V>;
80
81    fn pipeline<'a>(
82        _graphrecord: &'a GraphRecord,
83        prepared: Self::Prepared<'a>,
84    ) -> QueryResult<ElementPipeline<'a, Bare<V>, Self>> {
85        Ok(string_rebuild_map_bare::<V>(
86            Self::LABEL,
87            move |label, value| slice_string(label, &value, prepared.0, prepared.1),
88        ))
89    }
90
91    fn estimate(&self, input: Estimate, _stats: &Stats) -> Estimate {
92        input.with_unknown_distinct()
93    }
94}
95
96impl<O: Apply<SliceOperation>> Slice for O {
97    type ReturnOperand = O::Output;
98
99    fn slice(&self, start: usize, end: usize) -> Self::ReturnOperand {
100        Self::ReturnOperand::new(OperationContext::new(
101            self.clone(),
102            SliceOperation { start, end },
103        ))
104    }
105}
106
107operation_manifest! {
108    SliceOperation {
109        method: Slice::slice;
110        scope: element;
111
112        kernel {
113            parameters: <I: IndexDomain, V: StringValue>;
114            field: start: usize;
115            field: end: usize;
116            input: Indexed<I, V>;
117            output: Indexed<I, V>;
118            emission: Preserving;
119        }
120        kernel {
121            parameters: <V: StringValue + BareValueDomain>;
122            field: start: usize;
123            field: end: usize;
124            input: Bare<V>;
125            output: Bare<V>;
126            emission: Preserving;
127        }
128    }
129}