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}