typedb_driver/answer/
concept_row.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20use std::{
21    fmt,
22    fmt::{Debug, Formatter},
23    sync::Arc,
24};
25
26use itertools::Itertools;
27
28use crate::{
29    analyze::{conjunction::ConjunctionID, pipeline::Pipeline},
30    answer::QueryType,
31    common::Result,
32    concept::Concept,
33    error::ConceptError,
34};
35
36#[derive(Debug)]
37pub struct ConceptRowHeader {
38    pub column_names: Vec<String>,
39    pub query_type: QueryType,
40    pub query_structure: Option<Pipeline>,
41}
42
43impl ConceptRowHeader {
44    fn get_index(&self, name: &str) -> Option<usize> {
45        self.column_names.iter().find_position(|column_name| **column_name == name).map(|(pos, _)| pos)
46    }
47}
48
49/// A single row of concepts representing substitutions for variables in the query.
50/// Contains a Header (column names and query type), and the row of optional concepts.
51/// An empty concept in a column means the variable does not have a substitution in this answer.
52#[derive(Clone)]
53pub struct ConceptRow {
54    header: Arc<ConceptRowHeader>,
55    pub row: Vec<Option<Concept>>,
56    involved_conjunctions: Option<Vec<u8>>,
57}
58
59impl ConceptRow {
60    pub fn new(
61        header: Arc<ConceptRowHeader>,
62        row: Vec<Option<Concept>>,
63        involved_conjunctions: Option<Vec<u8>>,
64    ) -> Self {
65        Self { header, involved_conjunctions, row }
66    }
67
68    /// Retrieve the row column names (shared by all elements in this stream).
69    ///
70    /// # Examples
71    ///
72    /// ```rust
73    /// concept_row.get_column_names()
74    /// ```
75    pub fn get_column_names(&self) -> &[String] {
76        &self.header.column_names
77    }
78
79    /// Retrieve the executed query's type (shared by all elements in this stream).
80    ///
81    /// # Examples
82    ///
83    /// ```rust
84    /// concept_row.get_query_type()
85    /// ```
86    pub fn get_query_type(&self) -> QueryType {
87        self.header.query_type
88    }
89
90    /// Retrieve the executed query's structure from the <code>ConceptRow</code>'s header, if set.
91    /// It must be requested via "include query structure" in <code>QueryOptions</code>
92    ///
93    /// # Examples
94    ///
95    /// ```rust
96    /// concept_row.get_query_structure()
97    /// ```
98    pub fn get_query_structure(&self) -> Option<&Pipeline> {
99        self.header.query_structure.as_ref()
100    }
101
102    /// Retrieve the <code>ConjunctionID</code>s of <code>Conjunction</code>s that answered this row.
103    ///
104    /// # Examples
105    ///
106    /// ```rust
107    /// concept_row.get_involved_conjunctions()
108    /// ```
109    pub fn get_involved_conjunctions(&self) -> Option<impl Iterator<Item = ConjunctionID> + '_> {
110        self.involved_conjunctions.as_ref().map(|involved| {
111            (0..(involved.len() * 8))
112                .filter(|conjunction| {
113                    let index = conjunction / 8;
114                    let mask = 1 << (conjunction % 8);
115                    involved[index] & mask != 0
116                })
117                .map(ConjunctionID)
118        })
119    }
120
121    /// Like <code>ConceptRow::get_involved_conjunctions</code> but clones the underlying data.
122    /// Meant for simpler lifetimes over FFI.
123    pub fn get_involved_conjunctions_cloned(&self) -> Option<impl Iterator<Item = ConjunctionID> + 'static> {
124        let cloned = self.involved_conjunctions.clone();
125        cloned.map(|involved| {
126            (0..(involved.len() * 8))
127                .filter(move |conjunction| {
128                    let index = conjunction / 8;
129                    let mask = 1 << (conjunction % 8);
130                    involved[index] & mask != 0
131                })
132                .map(ConjunctionID)
133        })
134    }
135
136    /// Retrieves a concept for a given variable. Returns an empty optional if
137    /// the variable name has an empty answer. Returns an error if the variable name is not present.
138    ///
139    /// # Arguments
140    ///
141    /// * `var_name` — The variable name in the row to retrieve
142    ///
143    /// # Examples
144    ///
145    /// ```rust
146    /// concept_row.get(var_name)
147    /// ```
148    pub fn get(&self, column_name: &str) -> Result<Option<&Concept>> {
149        let index = self
150            .header
151            .get_index(column_name)
152            .ok_or(ConceptError::UnavailableRowVariable { variable: column_name.to_string() })?;
153        self.get_index(index)
154    }
155
156    /// Retrieves a concept for a given column index. Returns an empty optional if the index
157    /// points to an empty answer. Returns an error if the index is not in the row's range.
158    ///
159    /// # Arguments
160    ///
161    /// * `column_index` — The position in the row to retrieve
162    ///
163    /// # Examples
164    ///
165    /// ```rust
166    /// concept_row.get_position(column_index)
167    /// ```
168    pub fn get_index(&self, column_index: usize) -> Result<Option<&Concept>> {
169        let concept = self.row.get(column_index).ok_or(ConceptError::UnavailableRowIndex { index: column_index })?;
170        Ok(concept.as_ref())
171    }
172
173    /// Produces an iterator over all concepts in this `ConceptRow`, skipping empty results
174    ///
175    /// # Examples
176    ///
177    /// ```rust
178    /// concept_row.concepts()
179    /// ```
180    pub fn get_concepts(&self) -> impl Iterator<Item = &Concept> {
181        self.row.iter().filter_map(|concept| concept.as_ref())
182    }
183}
184
185impl PartialEq for ConceptRow {
186    fn eq(&self, other: &Self) -> bool {
187        self.row.eq(&other.row) && self.involved_conjunctions.eq(&other.involved_conjunctions)
188    }
189}
190
191impl fmt::Display for ConceptRow {
192    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
193        fmt::Debug::fmt(self, f)
194    }
195}
196
197impl fmt::Debug for ConceptRow {
198    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
199        write!(f, "|")?;
200        for (concept, name) in self.row.iter().zip(self.header.column_names.iter()) {
201            match concept {
202                None => write!(f, "  ${}: empty  ", name)?,
203                Some(concept) => write!(f, "  ${}: {}  |", name, concept)?,
204            }
205        }
206        Ok(())
207    }
208}