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::{answer::QueryType, common::Result, concept::Concept, error::ConceptError};
29
30#[derive(Debug, PartialEq)]
31pub struct ConceptRowHeader {
32    pub column_names: Vec<String>,
33    pub query_type: QueryType,
34}
35
36impl ConceptRowHeader {
37    fn get_index(&self, name: &str) -> Option<usize> {
38        self.column_names.iter().find_position(|column_name| **column_name == name).map(|(pos, _)| pos)
39    }
40}
41
42/// A single row of concepts representing substitutions for variables in the query.
43/// Contains a Header (column names and query type), and the row of optional concepts.
44/// An empty concept in a column means the variable does not have a substitution in this answer.
45#[derive(Clone, PartialEq)]
46pub struct ConceptRow {
47    header: Arc<ConceptRowHeader>,
48    pub row: Vec<Option<Concept>>,
49}
50
51impl ConceptRow {
52    pub fn new(header: Arc<ConceptRowHeader>, row: Vec<Option<Concept>>) -> Self {
53        Self { header, row }
54    }
55
56    /// Retrieve the row column names (shared by all elements in this stream).
57    ///
58    /// # Examples
59    ///
60    /// ```rust
61    /// concept_row.get_column_names()
62    /// ```
63    pub fn get_column_names(&self) -> &[String] {
64        &self.header.column_names
65    }
66
67    /// Retrieve the executed query's type (shared by all elements in this stream).
68    ///
69    /// # Examples
70    ///
71    /// ```rust
72    /// concept_row.get_query_type()
73    /// ```
74    pub fn get_query_type(&self) -> QueryType {
75        self.header.query_type
76    }
77
78    /// Retrieves a concept for a given variable. Returns an empty optional if
79    /// the variable name has an empty answer. Returns an error if the variable name is not present.
80    ///
81    /// # Arguments
82    ///
83    /// * `var_name` — The variable name in the row to retrieve
84    ///
85    /// # Examples
86    ///
87    /// ```rust
88    /// concept_row.get(var_name)
89    /// ```
90    pub fn get(&self, column_name: &str) -> Result<Option<&Concept>> {
91        let index = self
92            .header
93            .get_index(column_name)
94            .ok_or(ConceptError::UnavailableRowVariable { variable: column_name.to_string() })?;
95        self.get_index(index)
96    }
97
98    /// Retrieves a concept for a given column index. Returns an empty optional if the index
99    /// points to an empty answer. Returns an error if the index is not in the row's range.
100    ///
101    /// # Arguments
102    ///
103    /// * `column_index` — The position in the row to retrieve
104    ///
105    /// # Examples
106    ///
107    /// ```rust
108    /// concept_row.get_position(column_index)
109    /// ```
110    pub fn get_index(&self, column_index: usize) -> Result<Option<&Concept>> {
111        let concept = self.row.get(column_index).ok_or(ConceptError::UnavailableRowIndex { index: column_index })?;
112        Ok(concept.as_ref())
113    }
114
115    /// Produces an iterator over all concepts in this `ConceptRow`, skipping empty results
116    ///
117    /// # Examples
118    ///
119    /// ```rust
120    /// concept_row.concepts()
121    /// ```
122    pub fn get_concepts(&self) -> impl Iterator<Item = &Concept> {
123        self.row.iter().filter_map(|concept| concept.as_ref())
124    }
125}
126
127impl fmt::Display for ConceptRow {
128    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
129        fmt::Debug::fmt(self, f)
130    }
131}
132
133impl fmt::Debug for ConceptRow {
134    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
135        write!(f, "|")?;
136        for (concept, name) in self.row.iter().zip(self.header.column_names.iter()) {
137            match concept {
138                None => write!(f, "  ${}: empty  ", name)?,
139                Some(concept) => write!(f, "  ${}: {}  |", name, concept)?,
140            }
141        }
142        Ok(())
143    }
144}