Repository: limebit/medmodels
Pull request #403: refactor: query engine return types
Author: login: JabobKrauskopf
Created: 2025-05-13T09:35:14Z | Merged: 2025-05-14T14:32:55Z

This PR refactors the rust return operand types. It also adds the possibility of returning multiple operands from queries. Getting the concrete return value type for these lists is quite difficult in python, so for new the generic return value type alias is returned and users will have to assert to get the concrete type (issue #404).


Diff:
diff --git a/crates/medmodels-core/src/medrecord/mod.rs b/crates/medmodels-core/src/medrecord/mod.rs
index 00fa97c4..97281127 100644
--- a/crates/medmodels-core/src/medrecord/mod.rs
+++ b/crates/medmodels-core/src/medrecord/mod.rs
@@ -12,8 +12,10 @@ pub use self::{
     group_mapping::Group,
     querying::{
         attributes::{
-            AttributesTreeOperand, MultipleAttributesComparisonOperand, MultipleAttributesOperand,
-            SingleAttributeComparisonOperand, SingleAttributeOperand,
+            AttributesTreeOperand, EdgeAttributesTreeOperand, EdgeMultipleAttributesOperand,
+            EdgeSingleAttributeOperand, MultipleAttributesComparisonOperand,
+            MultipleAttributesOperand, NodeAttributesTreeOperand, NodeMultipleAttributesOperand,
+            NodeSingleAttributeOperand, SingleAttributeComparisonOperand, SingleAttributeOperand,
         },
         edges::{
             EdgeIndexComparisonOperand, EdgeIndexOperand, EdgeIndicesComparisonOperand,
@@ -25,11 +27,12 @@ pub use self::{
         },
         traits::DeepClone,
         values::{
-            MultipleValuesComparisonOperand, MultipleValuesOperand, SingleValueComparisonOperand,
-            SingleValueOperand,
+            EdgeMultipleValuesOperand, EdgeSingleValueOperand, MultipleValuesComparisonOperand,
+            MultipleValuesOperand, NodeMultipleValuesOperand, NodeSingleValueOperand,
+            SingleValueComparisonOperand, SingleValueOperand,
         },
         wrapper::{CardinalityWrapper, Wrapper},
-        OptionalIndexWrapper, ReturnOperand, ReturnValue, Selection,
+        OptionalIndexWrapper, ReturnOperand, Selection,
     },
     schema::{AttributeDataType, AttributeSchema, AttributeType, GroupSchema, Schema, SchemaType},
 };
@@ -905,18 +908,18 @@ impl MedRecord {
         self.group_mapping.clear();
     }
 
-    pub fn query_nodes<Q, R>(&self, query: Q) -> Selection
+    pub fn query_nodes<'a, Q, R>(&'a self, query: Q) -> Selection<'a, R>
     where
         Q: FnOnce(&mut Wrapper<NodeOperand>) -> R,
-        R: Into<ReturnOperand>,
+        R: ReturnOperand<'a>,
     {
         Selection::new_node(self, query)
     }
 
-    pub fn query_edges<Q, R>(&self, query: Q) -> Selection
+    pub fn query_edges<'a, Q, R>(&'a self, query: Q) -> Selection<'a, R>
     where
         Q: FnOnce(&mut Wrapper<EdgeOperand>) -> R,
-        R: Into<ReturnOperand>,
+        R: ReturnOperand<'a>,
     {
         Selection::new_edge(self, query)
     }
diff --git a/crates/medmodels-core/src/medrecord/querying/mod.rs b/crates/medmodels-core/src/medrecord/querying/mod.rs
index 99cfead7..edd50999 100644
--- a/crates/medmodels-core/src/medrecord/querying/mod.rs
+++ b/crates/medmodels-core/src/medrecord/querying/mod.rs
@@ -23,6 +23,51 @@ use values::{
     NodeSingleValueOperand,
 };
 
+macro_rules! impl_return_operand_for_tuples {
+    ($($T:ident),+) => {
+        impl<'a, $($T: ReturnOperand<'a>),+> ReturnOperand<'a> for ($($T,)+) {
+            type ReturnValue = ($($T::ReturnValue,)+);
+
+            #[allow(non_snake_case)]
+            fn evaluate(self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
+                let ($($T,)+) = self;
+
+                $(let $T = $T.evaluate(medrecord)?;)+
+
+                Ok(($($T,)+))
+            }
+        }
+    };
+}
+
+macro_rules! impl_iterator_return_operand {
+    ($( $Operand:ident => $Item:ty ),* $(,)?) => {
+        $(
+            impl<'a> ReturnOperand<'a> for Wrapper<$Operand> {
+                type ReturnValue = Box<dyn Iterator<Item = $Item> + 'a>;
+
+                fn evaluate(self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
+                    Ok(Box::new(self.evaluate_backward(medrecord)?))
+                }
+            }
+        )*
+    };
+}
+
+macro_rules! impl_direct_return_operand {
+    ($( $Operand:ident => $ReturnValue:ty ),* $(,)?) => {
+        $(
+            impl<'a> ReturnOperand<'a> for Wrapper<$Operand> {
+                type ReturnValue = $ReturnValue;
+
+                fn evaluate(self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
+                    self.evaluate_backward(medrecord)
+                }
+            }
+        )*
+    };
+}
+
 pub trait Index: Eq + Clone + Hash + Display + GetAttributes {}
 
 impl Index for NodeIndex {}
@@ -101,203 +146,78 @@ impl<I: Index, T> From<(I, T)> for OptionalIndexWrapper<I, T> {
 }
 
 #[derive(Debug, Clone)]
-pub struct Selection<'a> {
+pub struct Selection<'a, R: ReturnOperand<'a>> {
     medrecord: &'a MedRecord,
-    return_operand: ReturnOperand,
+    return_operand: R,
 }
 
-impl<'a> Selection<'a> {
-    pub fn new_node<Q, R>(medrecord: &'a MedRecord, query: Q) -> Self
+impl<'a, R: ReturnOperand<'a>> Selection<'a, R> {
+    pub fn new_node<Q>(medrecord: &'a MedRecord, query: Q) -> Self
     where
         Q: FnOnce(&mut Wrapper<NodeOperand>) -> R,
-        R: Into<ReturnOperand>,
     {
         let mut operand = Wrapper::<NodeOperand>::new(None);
 
         Self {
             medrecord,
-            return_operand: query(&mut operand).into(),
+            return_operand: query(&mut operand),
         }
     }
 
-    pub fn new_edge<Q, R>(medrecord: &'a MedRecord, query: Q) -> Self
+    pub fn new_edge<Q>(medrecord: &'a MedRecord, query: Q) -> Self
     where
         Q: FnOnce(&mut Wrapper<EdgeOperand>) -> R,
-        R: Into<ReturnOperand>,
     {
         let mut operand = Wrapper::<EdgeOperand>::new(None);
 
         Self {
             medrecord,
-            return_operand: query(&mut operand).into(),
+            return_operand: query(&mut operand),
         }
     }
 
-    pub fn evaluate(self) -> MedRecordResult<ReturnValue<'a>> {
-        let result = match self.return_operand {
-            ReturnOperand::NodeAttributesTree(operand) => ReturnValue::NodeAttributesTree(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeAttributesTree(operand) => ReturnValue::EdgeAttributesTree(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeMultipleAttributes(operand) => ReturnValue::NodeMultipleAttributes(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeMultipleAttributes(operand) => ReturnValue::EdgeMultipleAttributes(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeSingleAttribute(operand) => {
-                ReturnValue::NodeSingleAttribute(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeSingleAttribute(operand) => {
-                ReturnValue::EdgeSingleAttribute(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeIndices(operand) => {
-                ReturnValue::EdgeIndices(Box::new(operand.evaluate_backward(self.medrecord)?))
-            }
-            ReturnOperand::EdgeIndex(operand) => {
-                ReturnValue::EdgeIndex(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::NodeIndices(operand) => {
-                ReturnValue::NodeIndices(Box::new(operand.evaluate_backward(self.medrecord)?))
-            }
-            ReturnOperand::NodeIndex(operand) => {
-                ReturnValue::NodeIndex(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::NodeMultipleValues(operand) => ReturnValue::NodeMultipleValues(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeMultipleValues(operand) => ReturnValue::EdgeMultipleValues(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeSingleValue(operand) => {
-                ReturnValue::NodeSingleValue(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeSingleValue(operand) => {
-                ReturnValue::EdgeSingleValue(operand.evaluate_backward(self.medrecord)?)
-            }
-        };
-
-        Ok(result)
-    }
-}
-
-#[derive(Debug, Clone)]
-pub enum ReturnOperand {
-    NodeAttributesTree(Wrapper<NodeAttributesTreeOperand>),
-    EdgeAttributesTree(Wrapper<EdgeAttributesTreeOperand>),
-    NodeMultipleAttributes(Wrapper<NodeMultipleAttributesOperand>),
-    EdgeMultipleAttributes(Wrapper<EdgeMultipleAttributesOperand>),
-    NodeSingleAttribute(Wrapper<NodeSingleAttributeOperand>),
-    EdgeSingleAttribute(Wrapper<EdgeSingleAttributeOperand>),
-    EdgeIndices(Wrapper<EdgeIndicesOperand>),
-    EdgeIndex(Wrapper<EdgeIndexOperand>),
-    NodeIndices(Wrapper<NodeIndicesOperand>),
-    NodeIndex(Wrapper<NodeIndexOperand>),
-    NodeMultipleValues(Wrapper<NodeMultipleValuesOperand>),
-    EdgeMultipleValues(Wrapper<EdgeMultipleValuesOperand>),
-    NodeSingleValue(Wrapper<NodeSingleValueOperand>),
-    EdgeSingleValue(Wrapper<EdgeSingleValueOperand>),
-}
-
-impl From<Wrapper<NodeAttributesTreeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeAttributesTreeOperand>) -> Self {
-        Self::NodeAttributesTree(operand)
-    }
-}
-
-impl From<Wrapper<EdgeAttributesTreeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeAttributesTreeOperand>) -> Self {
-        Self::EdgeAttributesTree(operand)
-    }
-}
-
-impl From<Wrapper<NodeMultipleAttributesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeMultipleAttributesOperand>) -> Self {
-        Self::NodeMultipleAttributes(operand)
-    }
-}
-
-impl From<Wrapper<EdgeMultipleAttributesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeMultipleAttributesOperand>) -> Self {
-        Self::EdgeMultipleAttributes(operand)
-    }
-}
-
-impl From<Wrapper<NodeSingleAttributeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeSingleAttributeOperand>) -> Self {
-        Self::NodeSingleAttribute(operand)
-    }
-}
-
-impl From<Wrapper<EdgeSingleAttributeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeSingleAttributeOperand>) -> Self {
-        Self::EdgeSingleAttribute(operand)
-    }
-}
-
-impl From<Wrapper<EdgeIndicesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeIndicesOperand>) -> Self {
-        Self::EdgeIndices(operand)
-    }
-}
-
-impl From<Wrapper<EdgeIndexOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeIndexOperand>) -> Self {
-        Self::EdgeIndex(operand)
-    }
-}
-
-impl From<Wrapper<NodeIndicesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeIndicesOperand>) -> Self {
-        Self::NodeIndices(operand)
-    }
-}
-
-impl From<Wrapper<NodeIndexOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeIndexOperand>) -> Self {
-        Self::NodeIndex(operand)
-    }
-}
-
-impl From<Wrapper<NodeMultipleValuesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeMultipleValuesOperand>) -> Self {
-        Self::NodeMultipleValues(operand)
-    }
-}
-
-impl From<Wrapper<EdgeMultipleValuesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeMultipleValuesOperand>) -> Self {
-        Self::EdgeMultipleValues(operand)
-    }
-}
-
-impl From<Wrapper<NodeSingleValueOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeSingleValueOperand>) -> Self {
-        Self::NodeSingleValue(operand)
-    }
-}
-
-impl From<Wrapper<EdgeSingleValueOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeSingleValueOperand>) -> Self {
-        Self::EdgeSingleValue(operand)
-    }
-}
-
-pub enum ReturnValue<'a> {
-    NodeAttributesTree(Box<dyn Iterator<Item = (&'a NodeIndex, Vec<MedRecordAttribute>)> + 'a>),
-    EdgeAttributesTree(Box<dyn Iterator<Item = (&'a EdgeIndex, Vec<MedRecordAttribute>)> + 'a>),
-    NodeMultipleAttributes(Box<dyn Iterator<Item = (&'a NodeIndex, MedRecordAttribute)> + 'a>),
-    EdgeMultipleAttributes(Box<dyn Iterator<Item = (&'a EdgeIndex, MedRecordAttribute)> + 'a>),
-    NodeSingleAttribute(Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordAttribute>>),
-    EdgeSingleAttribute(Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordAttribute>>),
-    EdgeIndices(Box<dyn Iterator<Item = EdgeIndex> + 'a>),
-    EdgeIndex(Option<EdgeIndex>),
-    NodeIndices(Box<dyn Iterator<Item = NodeIndex> + 'a>),
-    NodeIndex(Option<NodeIndex>),
-    NodeMultipleValues(Box<dyn Iterator<Item = (&'a NodeIndex, MedRecordValue)> + 'a>),
-    EdgeMultipleValues(Box<dyn Iterator<Item = (&'a EdgeIndex, MedRecordValue)> + 'a>),
-    NodeSingleValue(Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordValue>>),
-    EdgeSingleValue(Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordValue>>),
-}
+    pub fn evaluate(self) -> MedRecordResult<R::ReturnValue> {
+        self.return_operand.evaluate(self.medrecord)
+    }
+}
+
+pub trait ReturnOperand<'a> {
+    type ReturnValue;
+
+    fn evaluate(self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue>;
+}
+
+impl_iterator_return_operand!(
+    NodeAttributesTreeOperand     => (&'a NodeIndex, Vec<MedRecordAttribute>),
+    EdgeAttributesTreeOperand     => (&'a EdgeIndex, Vec<MedRecordAttribute>),
+    NodeMultipleAttributesOperand => (&'a NodeIndex, MedRecordAttribute),
+    EdgeMultipleAttributesOperand => (&'a EdgeIndex, MedRecordAttribute),
+    EdgeIndicesOperand            => EdgeIndex,
+    NodeIndicesOperand            => NodeIndex,
+    NodeMultipleValuesOperand     => (&'a NodeIndex, MedRecordValue),
+    EdgeMultipleValuesOperand     => (&'a EdgeIndex, MedRecordValue),
+);
+
+impl_direct_return_operand!(
+    NodeSingleAttributeOperand => Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordAttribute>>,
+    EdgeSingleAttributeOperand => Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordAttribute>>,
+    EdgeIndexOperand           => Option<EdgeIndex>,
+    NodeIndexOperand           => Option<NodeIndex>,
+    NodeSingleValueOperand     => Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordValue>>,
+    EdgeSingleValueOperand     => Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordValue>>,
+);
+
+impl_return_operand_for_tuples!(R1, R2);
+impl_return_operand_for_tuples!(R1, R2, R3);
+impl_return_operand_for_tuples!(R1, R2, R3, R4);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15);
diff --git a/crates/medmodels-core/src/medrecord/querying/values/mod.rs b/crates/medmodels-core/src/medrecord/querying/values/mod.rs
index 44ac4a76..6d21f9dd 100644
--- a/crates/medmodels-core/src/medrecord/querying/values/mod.rs
+++ b/crates/medmodels-core/src/medrecord/querying/values/mod.rs
@@ -146,7 +146,6 @@ impl GetValues<EdgeIndex> for EdgeOperand {
     }
 }
 
-#[allow(clippy::enum_variant_names)]
 #[derive(Debug, Clone)]
 pub enum Context<O: Operand> {
     Operand((O, MedRecordAttribute)),
diff --git a/medmodels/medrecord/medrecord.py b/medmodels/medrecord/medrecord.py
index 15401afa..6876b2a0 100644
--- a/medmodels/medrecord/medrecord.py
+++ b/medmodels/medrecord/medrecord.py
@@ -61,6 +61,7 @@
     NodeSingleValueQueryResult,
     PyQueryReturnOperand,
     QueryResult,
+    QueryReturnOperand,
 )
 from medmodels.medrecord.schema import Schema
 from medmodels.medrecord.types import (
@@ -127,6 +128,36 @@ def process_edges_dataframe(
     return edges_polars, edges[1], edges[2]
 
 
+def _convert_queryreturnoperand_to_pyqueryreturnoperand(
+    operand: QueryReturnOperand,
+) -> PyQueryReturnOperand:
+    if isinstance(operand, (NodeAttributesTreeOperand, EdgeAttributesTreeOperand)):
+        return operand._attributes_tree_operand
+    if isinstance(
+        operand, (NodeMultipleAttributesOperand, EdgeMultipleAttributesOperand)
+    ):
+        return operand._multiple_attributes_operand
+    if isinstance(operand, (NodeSingleAttributeOperand, EdgeSingleAttributeOperand)):
+        return operand._single_attribute_operand
+    if isinstance(operand, EdgeIndicesOperand):
+        return operand._edge_indices_operand
+    if isinstance(operand, EdgeIndexOperand):
+        return operand._edge_index_operand
+    if isinstance(operand, NodeIndicesOperand):
+        return operand._node_indices_operand
+    if isinstance(operand, NodeIndexOperand):
+        return operand._node_index_operand
+    if isinstance(operand, (NodeMultipleValuesOperand, EdgeMultipleValuesOperand)):
+        return operand._multiple_values_operand
+    if isinstance(operand, Sequence):
+        return [
+            _convert_queryreturnoperand_to_pyqueryreturnoperand(operand)
+            for operand in operand
+        ]
+
+    return operand._single_value_operand
+
+
 class OverviewTable:
     """Class for the node/edge group overview table."""
 
@@ -1514,6 +1545,10 @@ def query_nodes(
     def query_nodes(
         self, query: Callable[[NodeOperand], EdgeSingleValueOperand]
     ) -> EdgeSingleValueQueryResult: ...
+    @overload
+    def query_nodes(
+        self, query: Callable[[NodeOperand], Sequence[QueryReturnOperand]]
+    ) -> List[QueryResult]: ...
 
     def query_nodes(self, query: NodeQuery) -> QueryResult:
         """Selects nodes from the MedRecord based on the provided query.
@@ -1528,32 +1563,7 @@ def query_nodes(self, query: NodeQuery) -> QueryResult:
         def _query(node: PyNodeOperand) -> PyQueryReturnOperand:
             result = query(NodeOperand._from_py_node_operand(node))
 
-            if isinstance(
-                result, (NodeAttributesTreeOperand, EdgeAttributesTreeOperand)
-            ):
-                return result._attributes_tree_operand
-            if isinstance(
-                result, (NodeMultipleAttributesOperand, EdgeMultipleAttributesOperand)
-            ):
-                return result._multiple_attributes_operand
-            if isinstance(
-                result, (NodeSingleAttributeOperand, EdgeSingleAttributeOperand)
-            ):
-                return result._single_attribute_operand
-            if isinstance(result, EdgeIndicesOperand):
-                return result._edge_indices_operand
-            if isinstance(result, EdgeIndexOperand):
-                return result._edge_index_operand
-            if isinstance(result, NodeIndicesOperand):
-                return result._node_indices_operand
-            if isinstance(result, NodeIndexOperand):
-                return result._node_index_operand
-            if isinstance(
-                result, (NodeMultipleValuesOperand, EdgeMultipleValuesOperand)
-            ):
-                return result._multiple_values_operand
-
-            return result._single_value_operand
+            return _convert_queryreturnoperand_to_pyqueryreturnoperand(result)
 
         return self._medrecord.query_nodes(_query)
 
@@ -1613,6 +1623,10 @@ def query_edges(
     def query_edges(
         self, query: Callable[[EdgeOperand], EdgeSingleValueOperand]
     ) -> EdgeSingleValueQueryResult: ...
+    @overload
+    def query_edges(
+        self, query: Callable[[EdgeOperand], Sequence[QueryReturnOperand]]
+    ) -> List[QueryResult]: ...
 
     def query_edges(self, query: EdgeQuery) -> QueryResult:
         """Selects edges from the MedRecord based on the provided query.
@@ -1627,32 +1641,7 @@ def query_edges(self, query: EdgeQuery) -> QueryResult:
         def _query(edge: PyEdgeOperand) -> PyQueryReturnOperand:
             result = query(EdgeOperand._from_py_edge_operand(edge))
 
-            if isinstance(
-                result, (NodeAttributesTreeOperand, EdgeAttributesTreeOperand)
-            ):
-                return result._attributes_tree_operand
-            if isinstance(
-                result, (NodeMultipleAttributesOperand, EdgeMultipleAttributesOperand)
-            ):
-                return result._multiple_attributes_operand
-            if isinstance(
-                result, (NodeSingleAttributeOperand, EdgeSingleAttributeOperand)
-            ):
-                return result._single_attribute_operand
-            if isinstance(result, EdgeIndicesOperand):
-                return result._edge_indices_operand
-            if isinstance(result, EdgeIndexOperand):
-                return result._edge_index_operand
-            if isinstance(result, NodeIndicesOperand):
-                return result._node_indices_operand
-            if isinstance(result, NodeIndexOperand):
-                return result._node_index_operand
-            if isinstance(
-                result, (NodeMultipleValuesOperand, EdgeMultipleValuesOperand)
-            ):
-                return result._multiple_values_operand
-
-            return result._single_value_operand
+            return _convert_queryreturnoperand_to_pyqueryreturnoperand(result)
 
         return self._medrecord.query_edges(_query)
 
diff --git a/medmodels/medrecord/querying.py b/medmodels/medrecord/querying.py
index 50f8af7b..268be343 100644
--- a/medmodels/medrecord/querying.py
+++ b/medmodels/medrecord/querying.py
@@ -3,7 +3,7 @@
 from __future__ import annotations
 
 from enum import Enum
-from typing import Callable, Dict, List, Optional, Tuple, TypeAlias, Union
+from typing import Callable, Dict, List, Optional, Sequence, Tuple, TypeAlias, Union
 
 from medmodels._medmodels import (
     PyEdgeAttributesTreeOperand,
@@ -47,10 +47,11 @@
     PyEdgeMultipleValuesOperand,
     PyNodeSingleValueOperand,
     PyEdgeSingleValueOperand,
+    Sequence["PyQueryReturnOperand"],
 ]
 
 #: A type alias for a query return operand.
-QueryReturnOperand = Union[
+QueryReturnOperand: TypeAlias = Union[
     "NodeAttributesTreeOperand",
     "EdgeAttributesTreeOperand",
     "NodeMultipleAttributesOperand",
@@ -65,45 +66,46 @@
     "EdgeMultipleValuesOperand",
     "NodeSingleValueOperand",
     "EdgeSingleValueOperand",
+    Sequence["QueryReturnOperand"],
 ]
 
-NodeAttributesTreeQueryResult = Dict[NodeIndex, List[MedRecordAttribute]]
-EdgeAttributesTreeQueryResult = Dict[EdgeIndex, List[MedRecordAttribute]]
+NodeAttributesTreeQueryResult: TypeAlias = Dict[NodeIndex, List[MedRecordAttribute]]
+EdgeAttributesTreeQueryResult: TypeAlias = Dict[EdgeIndex, List[MedRecordAttribute]]
 
-NodeMultipleAttributesQueryResult = Dict[NodeIndex, MedRecordAttribute]
-EdgeMultipleAttributesQueryResult = Dict[EdgeIndex, MedRecordAttribute]
+NodeMultipleAttributesQueryResult: TypeAlias = Dict[NodeIndex, MedRecordAttribute]
+EdgeMultipleAttributesQueryResult: TypeAlias = Dict[EdgeIndex, MedRecordAttribute]
 
-NodeSingleAttributeQueryResult = Union[
+NodeSingleAttributeQueryResult: TypeAlias = Union[
     Optional[Tuple[NodeIndex, MedRecordAttribute]],
     Optional[MedRecordAttribute],
 ]
-EdgeSingleAttributeQueryResult = Union[
+EdgeSingleAttributeQueryResult: TypeAlias = Union[
     Optional[Tuple[EdgeIndex, MedRecordAttribute]],
     Optional[MedRecordAttribute],
 ]
 
-EdgeIndicesQueryResult = List[EdgeIndex]
+EdgeIndicesQueryResult: TypeAlias = List[EdgeIndex]
 
-EdgeIndexQueryResult = Optional[EdgeIndex]
+EdgeIndexQueryResult: TypeAlias = Optional[EdgeIndex]
 
-NodeIndicesQueryResult = List[NodeIndex]
+NodeIndicesQueryResult: TypeAlias = List[NodeIndex]
 
-NodeIndexQueryResult = Optional[NodeIndex]
+NodeIndexQueryResult: TypeAlias = Optional[NodeIndex]
 
-NodeMultipleValuesQueryResult = Dict[NodeIndex, MedRecordValue]
-EdgeMultipleValuesQueryResult = Dict[EdgeIndex, MedRecordValue]
+NodeMultipleValuesQueryResult: TypeAlias = Dict[NodeIndex, MedRecordValue]
+EdgeMultipleValuesQueryResult: TypeAlias = Dict[EdgeIndex, MedRecordValue]
 
-NodeSingleValueQueryResult = Union[
+NodeSingleValueQueryResult: TypeAlias = Union[
     Optional[Tuple[NodeIndex, MedRecordValue]],
     Optional[MedRecordValue],
 ]
-EdgeSingleValueQueryResult = Union[
+EdgeSingleValueQueryResult: TypeAlias = Union[
     Optional[Tuple[EdgeIndex, MedRecordValue]],
     Optional[MedRecordValue],
 ]
 
 #: A type alias for a query result.
-QueryResult = Union[
+QueryResult: TypeAlias = Union[
     NodeAttributesTreeQueryResult,
     EdgeAttributesTreeQueryResult,
     NodeMultipleAttributesQueryResult,
@@ -118,6 +120,7 @@
     EdgeMultipleValuesQueryResult,
     NodeSingleValueQueryResult,
     EdgeSingleValueQueryResult,
+    List["QueryResult"],
 ]
 
 NodeQuery: TypeAlias = Callable[["NodeOperand"], QueryReturnOperand]
diff --git a/medmodels/medrecord/types.py b/medmodels/medrecord/types.py
index d97cd914..f05c9bcb 100644
--- a/medmodels/medrecord/types.py
+++ b/medmodels/medrecord/types.py
@@ -193,6 +193,19 @@ def is_node_index(value: object) -> TypeIs[NodeIndex]:
     return is_medrecord_attribute(value)
 
 
+def is_node_index_list(value: object) -> TypeIs[NodeIndexInputList]:
+    """Check if a value is a valid list of node indices.
+
+    Args:
+        value (object): The value to check.
+
+    Returns:
+        TypeIs[NodeIndexInputList]: True if the value is a valid list of node indices,
+            otherwise False.
+    """
+    return isinstance(value, list) and all(is_node_index(input) for input in value)
+
+
 def is_edge_index(value: object) -> TypeIs[EdgeIndex]:
     """Check if a value is a valid edge index.
 
@@ -205,6 +218,19 @@ def is_edge_index(value: object) -> TypeIs[EdgeIndex]:
     return isinstance(value, int)
 
 
+def is_edge_index_list(value: object) -> TypeIs[EdgeIndexInputList]:
+    """Check if a value is a valid list of edge indices.
+
+    Args:
+        value (object): The value to check.
+
+    Returns:
+        TypeIs[EdgeIndexInputList]: True if the value is a valid list of edge indices,
+            otherwise False.
+    """
+    return isinstance(value, list) and all(is_edge_index(input) for input in value)
+
+
 def is_group(value: object) -> TypeIs[Group]:
     """Check if a value is a valid group.
 
diff --git a/pyproject.toml b/pyproject.toml
index 61836fe4..2d8cbf34 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -145,7 +145,7 @@ ignore = [
 ]
 
 [tool.ruff.lint.per-file-ignores]
-"tests/*" = ["D", "DOC"]
+"tests/*" = ["D", "DOC", "C901"]
 "*/__init__.py" = ["D", "DOC"]
 
 [tool.ruff.lint.pydocstyle]
diff --git a/rustmodels/src/medrecord/datatype.rs b/rustmodels/src/medrecord/datatype.rs
index 96682b6a..b70021f0 100644
--- a/rustmodels/src/medrecord/datatype.rs
+++ b/rustmodels/src/medrecord/datatype.rs
@@ -81,7 +81,9 @@ pub(crate) fn convert_pyobject_to_datatype(ob: &Bound<'_, pyo3::PyAny>) -> PyRes
     }
 
     fn convert_union(ob: &Bound<'_, pyo3::PyAny>) -> PyResult<DataType> {
-        let union = ob.extract::<PyRef<PyUnion>>()?;
+        let union = ob
+            .extract::<PyRef<PyUnion>>()
+            .expect("Extraction must succeed");
 
         let dtypes = union.0.clone();
 
@@ -92,7 +94,9 @@ pub(crate) fn convert_pyobject_to_datatype(ob: &Bound<'_, pyo3::PyAny>) -> PyRes
     }
 
     fn convert_option(ob: &Bound<'_, pyo3::PyAny>) -> PyResult<DataType> {
-        let option = ob.extract::<PyRef<PyOption>>()?;
+        let option = ob
+            .extract::<PyRef<PyOption>>()
+            .expect("Extraction must succeed");
 
         Ok(DataType::Option(Box::new(option.0.clone().into())))
     }
diff --git a/rustmodels/src/medrecord/mod.rs b/rustmodels/src/medrecord/mod.rs
index d7fbc179..f94f2873 100644
--- a/rustmodels/src/medrecord/mod.rs
+++ b/rustmodels/src/medrecord/mod.rs
@@ -17,7 +17,7 @@ use medmodels_core::{
 };
 use pyo3::{prelude::*, types::PyFunction};
 use pyo3_polars::PyDataFrame;
-use querying::{edges::PyEdgeOperand, nodes::PyNodeOperand, parse_query_result, PyReturnValue};
+use querying::{edges::PyEdgeOperand, nodes::PyNodeOperand, PyReturnOperand, PyReturnValue};
 use schema::PySchema;
 use std::collections::HashMap;
 use traits::DeepInto;
@@ -706,11 +706,12 @@ impl PyMedRecord {
                     .call1((PyNodeOperand::from(node.clone()),))
                     .expect("Call must succeed");
 
-                parse_query_result(result)
+                result
+                    .extract::<PyReturnOperand>()
+                    .expect("Extraction must succeed")
             })
             .evaluate()
-            .map_err(PyMedRecordError::from)?
-            .into())
+            .map_err(PyMedRecordError::from)?)
     }
 
     pub fn query_edges(&self, query: &Bound<'_, PyFunction>) -> PyResult<PyReturnValue> {
@@ -721,11 +722,12 @@ impl PyMedRecord {
                     .call1((PyEdgeOperand::from(edge.clone()),))
                     .expect("Call must succeed");
 
-                parse_query_result(result)
+                result
+                    .extract::<PyReturnOperand>()
+                    .expect("Extraction must succeed")
             })
             .evaluate()
-            .map_err(PyMedRecordError::from)?
-            .into())
+            .map_err(PyMedRecordError::from)?)
     }
 
     pub fn clone(&self) -> Self {
diff --git a/rustmodels/src/medrecord/querying/attributes.rs b/rustmodels/src/medrecord/querying/attributes.rs
index d315d147..aa907dc6 100644
--- a/rustmodels/src/medrecord/querying/attributes.rs
+++ b/rustmodels/src/medrecord/querying/attributes.rs
@@ -1,3 +1,5 @@
+use std::ops::Deref;
+
 use super::values::{PyEdgeMultipleValuesOperand, PyNodeMultipleValuesOperand};
 use crate::medrecord::{attribute::PyMedRecordAttribute, errors::PyMedRecordError};
 use medmodels_core::{
@@ -29,6 +31,14 @@ impl From<PySingleAttributeComparisonOperand> for SingleAttributeComparisonOpera
     }
 }
 
+impl Deref for PySingleAttributeComparisonOperand {
+    type Target = SingleAttributeComparisonOperand;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
 impl FromPyObject<'_> for PySingleAttributeComparisonOperand {
     fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
         if let Ok(attribute) = ob.extract::<PyMedRecordAttribute>() {
@@ -64,6 +74,14 @@ impl From<PyMultipleAttributesComparisonOperand> for MultipleAttributesCompariso
     }
 }
 
+impl Deref for PyMultipleAttributesComparisonOperand {
+    type Target = MultipleAttributesComparisonOperand;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
 impl FromPyObject<'_> for PyMultipleAttributesComparisonOperand {
     fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
         if let Ok(values) = ob.extract::<Vec<PyMedRecordAttribute>>() {
@@ -106,6 +124,14 @@ macro_rules! implement_attributes_tree_operand {
             }
         }
 
+        impl Deref for $name {
+            type Target = Wrapper<AttributesTreeOperand<$generic>>;
+
+            fn deref(&self) -> &Self::Target {
+                &self.0
+            }
+        }
+
         #[pymethods]
         impl $name {
             pub fn max(&self) -> $multiple_attributes_operand {
@@ -299,6 +325,14 @@ macro_rules! implement_multiple_attributes_operand {
             }
         }
 
+        impl Deref for $name {
+            type Target = Wrapper<MultipleAttributesOperand<$generic>>;
+
+            fn deref(&self) -> &Self::Target {
+                &self.0
+            }
+        }
+
         #[pymethods]
         impl $name {
             pub fn max(&self) -> $py_single_attribute_operand {
@@ -498,6 +532,14 @@ macro_rules! implement_single_attribute_operand {
             }
         }
 
+        impl Deref for $name {
+            type Target = Wrapper<SingleAttributeOperand<$generic>>;
+
+            fn deref(&self) -> &Self::Target {
+                &self.0
+            }
+        }
+
         #[pymethods]
         impl $name {
             pub fn greater_than(&self, attribute: PySingleAttributeComparisonOperand) {
diff --git a/rustmodels/src/medrecord/querying/mod.rs b/rustmodels/src/medrecord/querying/mod.rs
index 2e6ea6c1..ae2e134b 100644
--- a/rustmodels/src/medrecord/querying/mod.rs
+++ b/rustmodels/src/medrecord/querying/mod.rs
@@ -3,9 +3,11 @@ pub mod edges;
 pub mod nodes;
 pub mod values;
 
+use crate::gil_hash_map::GILHashMap;
+
 use super::{
     attribute::PyMedRecordAttribute, errors::PyMedRecordError, traits::DeepFrom,
-    value::PyMedRecordValue, PyNodeIndex,
+    value::PyMedRecordValue, Lut, PyNodeIndex,
 };
 use attributes::{
     PyEdgeAttributesTreeOperand, PyEdgeMultipleAttributesOperand, PyEdgeSingleAttributeOperand,
@@ -13,14 +15,20 @@ use attributes::{
 };
 use edges::{PyEdgeIndexOperand, PyEdgeIndicesOperand};
 use medmodels_core::{
-    errors::MedRecordError,
+    errors::{MedRecordError, MedRecordResult},
     medrecord::{
-        CardinalityWrapper, MedRecordAttribute, OptionalIndexWrapper, ReturnOperand, ReturnValue,
+        CardinalityWrapper, EdgeAttributesTreeOperand, EdgeIndexOperand, EdgeIndicesOperand,
+        EdgeMultipleAttributesOperand, EdgeMultipleValuesOperand, EdgeSingleAttributeOperand,
+        EdgeSingleValueOperand, MedRecordAttribute, NodeAttributesTreeOperand, NodeIndexOperand,
+        NodeIndicesOperand, NodeMultipleAttributesOperand, NodeMultipleValuesOperand,
+        NodeSingleAttributeOperand, NodeSingleValueOperand, OptionalIndexWrapper, ReturnOperand,
+        Wrapper,
     },
+    MedRecord,
 };
 use nodes::{PyNodeIndexOperand, PyNodeIndicesOperand};
 use pyo3::{
-    types::{PyAnyMethods, PyNone},
+    types::{PyAnyMethods, PyList, PyNone},
     Bound, FromPyObject, IntoPyObject, IntoPyObjectExt, PyAny, PyErr, PyResult, Python,
 };
 use std::collections::HashMap;
@@ -29,133 +37,308 @@ use values::{
     PyNodeSingleValueOperand,
 };
 
-pub fn parse_query_result(result: Bound<'_, PyAny>) -> ReturnOperand {
-    if result.is_instance_of::<PyNodeAttributesTreeOperand>() {
-        ReturnOperand::NodeAttributesTree(
-            result
-                .extract::<PyNodeAttributesTreeOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyEdgeAttributesTreeOperand>() {
-        ReturnOperand::EdgeAttributesTree(
-            result
-                .extract::<PyEdgeAttributesTreeOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyNodeMultipleAttributesOperand>() {
-        ReturnOperand::NodeMultipleAttributes(
-            result
-                .extract::<PyNodeMultipleAttributesOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyEdgeMultipleAttributesOperand>() {
-        ReturnOperand::EdgeMultipleAttributes(
-            result
-                .extract::<PyEdgeMultipleAttributesOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyNodeSingleAttributeOperand>() {
-        ReturnOperand::NodeSingleAttribute(
-            result
-                .extract::<PyNodeSingleAttributeOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyEdgeSingleAttributeOperand>() {
-        ReturnOperand::EdgeSingleAttribute(
-            result
-                .extract::<PyEdgeSingleAttributeOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyEdgeIndicesOperand>() {
-        ReturnOperand::EdgeIndices(
-            result
-                .extract::<PyEdgeIndicesOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyEdgeIndexOperand>() {
-        ReturnOperand::EdgeIndex(
-            result
-                .extract::<PyEdgeIndexOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyNodeIndicesOperand>() {
-        ReturnOperand::NodeIndices(
-            result
-                .extract::<PyNodeIndicesOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyNodeIndexOperand>() {
-        ReturnOperand::NodeIndex(
-            result
-                .extract::<PyNodeIndexOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyNodeMultipleValuesOperand>() {
-        ReturnOperand::NodeMultipleValues(
-            result
-                .extract::<PyNodeMultipleValuesOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyEdgeMultipleValuesOperand>() {
-        ReturnOperand::EdgeMultipleValues(
-            result
-                .extract::<PyEdgeMultipleValuesOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyNodeSingleValueOperand>() {
-        ReturnOperand::NodeSingleValue(
-            result
-                .extract::<PyNodeSingleValueOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else if result.is_instance_of::<PyEdgeSingleValueOperand>() {
-        ReturnOperand::EdgeSingleValue(
-            result
-                .extract::<PyEdgeSingleValueOperand>()
-                .expect("Extract must succeed")
-                .into(),
-        )
-    } else {
-        panic!("Query function is not a valid NodeQueryFunction")
+pub enum PyReturnOperand {
+    NodeAttributesTree(PyNodeAttributesTreeOperand),
+    EdgeAttributesTree(PyEdgeAttributesTreeOperand),
+    NodeMultipleAttributes(PyNodeMultipleAttributesOperand),
+    EdgeMultipleAttributes(PyEdgeMultipleAttributesOperand),
+    NodeSingleAttribute(PyNodeSingleAttributeOperand),
+    EdgeSingleAttribute(PyEdgeSingleAttributeOperand),
+    EdgeIndices(PyEdgeIndicesOperand),
+    EdgeIndex(PyEdgeIndexOperand),
+    NodeIndices(PyNodeIndicesOperand),
+    NodeIndex(PyNodeIndexOperand),
+    NodeMultipleValues(PyNodeMultipleValuesOperand),
+    EdgeMultipleValues(PyEdgeMultipleValuesOperand),
+    NodeSingleValue(PyNodeSingleValueOperand),
+    EdgeSingleValue(PyEdgeSingleValueOperand),
+    Vector(Vec<Self>),
+}
+
+impl<'a> ReturnOperand<'a> for PyReturnOperand {
+    type ReturnValue = PyReturnValue<'a>;
+
+    fn evaluate(self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
+        match self {
+            PyReturnOperand::NodeAttributesTree(operand) => {
+                Wrapper::<NodeAttributesTreeOperand>::from(operand)
+                    .evaluate(medrecord)
+                    .map(PyReturnValue::NodeAttributesTree)
+            }
+            PyReturnOperand::EdgeAttributesTree(operand) => {
+                Wrapper::<EdgeAttributesTreeOperand>::from(operand)
+                    .evaluate(medrecord)
+                    .map(PyReturnValue::EdgeAttributesTree)
+            }
+            PyReturnOperand::NodeMultipleAttributes(operand) => {
+                Wrapper::<NodeMultipleAttributesOperand>::from(operand)
+                    .evaluate(medrecord)
+                    .map(PyReturnValue::NodeMultipleAttributes)
+            }
+            PyReturnOperand::EdgeMultipleAttributes(operand) => {
+                Wrapper::<EdgeMultipleAttributesOperand>::from(operand)
+                    .evaluate(medrecord)
+                    .map(PyReturnValue::EdgeMultipleAttributes)
+            }
+            PyReturnOperand::NodeSingleAttribute(operand) => {
+                Wrapper::<NodeSingleAttributeOperand>::from(operand)
+                    .evaluate(medrecord)
+                    .map(PyReturnValue::NodeSingleAttribute)
+            }
+            PyReturnOperand::EdgeSingleAttribute(operand) => {
+                Wrapper::<EdgeSingleAttributeOperand>::from(operand)
+                    .evaluate(medrecord)
+                    .map(PyReturnValue::EdgeSingleAttribute)
+            }
+            PyReturnOperand::EdgeIndices(operand) => Wrapper::<EdgeIndicesOperand>::from(operand)
+                .evaluate(medrecord)
+                .map(PyReturnValue::EdgeIndices),
+            PyReturnOperand::EdgeIndex(operand) => Wrapper::<EdgeIndexOperand>::from(operand)
+                .evaluate(medrecord)
+                .map(PyReturnValue::EdgeIndex),
+            PyReturnOperand::NodeIndices(operand) => Wrapper::<NodeIndicesOperand>::from(operand)
+                .evaluate(medrecord)
+                .map(PyReturnValue::NodeIndices),
+            PyReturnOperand::NodeIndex(operand) => Wrapper::<NodeIndexOperand>::from(operand)
+                .evaluate(medrecord)
+                .map(PyReturnValue::NodeIndex),
+            PyReturnOperand::NodeMultipleValues(operand) => {
+                Wrapper::<NodeMultipleValuesOperand>::from(operand)
+                    .evaluate(medrecord)
+                    .map(PyReturnValue::NodeMultipleValues)
+            }
+            PyReturnOperand::EdgeMultipleValues(operand) => {
+                Wrapper::<EdgeMultipleValuesOperand>::from(operand)
+                    .evaluate(medrecord)
+                    .map(PyReturnValue::EdgeMultipleValues)
+            }
+            PyReturnOperand::NodeSingleValue(operand) => {
+                Wrapper::<NodeSingleValueOperand>::from(operand)
+                    .evaluate(medrecord)
+                    .map(PyReturnValue::NodeSingleValue)
+            }
+            PyReturnOperand::EdgeSingleValue(operand) => {
+                Wrapper::<EdgeSingleValueOperand>::from(operand)
+                    .evaluate(medrecord)
+                    .map(PyReturnValue::EdgeSingleValue)
+            }
+            PyReturnOperand::Vector(operand) => operand
+                .into_iter()
+                .map(|item| item.evaluate(medrecord))
+                .collect::<MedRecordResult<Vec<_>>>()
+                .map(PyReturnValue::Vector),
+        }
     }
 }
 
-#[repr(transparent)]
-pub struct PyReturnValue<'a>(ReturnValue<'a>);
+static RETURNOPERAND_CONVERSION_LUT: Lut<PyReturnOperand> = GILHashMap::new();
+
+pub(crate) fn convert_pyobject_to_pyreturnoperand(
+    ob: &Bound<'_, PyAny>,
+) -> PyResult<PyReturnOperand> {
+    fn convert_py_node_attributes_tree_operand(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::NodeAttributesTree(
+            ob.extract::<PyNodeAttributesTreeOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_edge_attributes_tree_operand(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::EdgeAttributesTree(
+            ob.extract::<PyEdgeAttributesTreeOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_node_multiple_attributes_operand(
+        ob: &Bound<'_, PyAny>,
+    ) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::NodeMultipleAttributes(
+            ob.extract::<PyNodeMultipleAttributesOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_edge_multiple_attributes_operand(
+        ob: &Bound<'_, PyAny>,
+    ) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::EdgeMultipleAttributes(
+            ob.extract::<PyEdgeMultipleAttributesOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_node_single_attribute_operand(
+        ob: &Bound<'_, PyAny>,
+    ) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::NodeSingleAttribute(
+            ob.extract::<PyNodeSingleAttributeOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_edge_single_attribute_operand(
+        ob: &Bound<'_, PyAny>,
+    ) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::EdgeSingleAttribute(
+            ob.extract::<PyEdgeSingleAttributeOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_edge_indices_operand(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::EdgeIndices(
+            ob.extract::<PyEdgeIndicesOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_edge_index_operand(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::EdgeIndex(
+            ob.extract::<PyEdgeIndexOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_node_indices_operand(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::NodeIndices(
+            ob.extract::<PyNodeIndicesOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_node_index_operand(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::NodeIndex(
+            ob.extract::<PyNodeIndexOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_node_multiple_values_operand(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::NodeMultipleValues(
+            ob.extract::<PyNodeMultipleValuesOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_edge_multiple_values_operand(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::EdgeMultipleValues(
+            ob.extract::<PyEdgeMultipleValuesOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_node_single_value_operand(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::NodeSingleValue(
+            ob.extract::<PyNodeSingleValueOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
+
+    fn convert_py_edge_single_value_operand(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::EdgeSingleValue(
+            ob.extract::<PyEdgeSingleValueOperand>()
+                .expect("Extraction must succeed"),
+        ))
+    }
 
-impl<'a> From<ReturnValue<'a>> for PyReturnValue<'a> {
-    fn from(value: ReturnValue<'a>) -> Self {
-        Self(value)
+    fn convert_py_list(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Ok(PyReturnOperand::Vector(
+            ob.extract::<Vec<PyReturnOperand>>()?,
+        ))
     }
+
+    fn throw_error(ob: &Bound<'_, PyAny>) -> PyResult<PyReturnOperand> {
+        Err(
+            PyMedRecordError::from(MedRecordError::ConversionError(format!(
+                "Failed to convert {} into query ReturnOperand",
+                ob,
+            )))
+            .into(),
+        )
+    }
+
+    let type_pointer = ob.get_type_ptr() as usize;
+
+    Python::with_gil(|py| {
+        RETURNOPERAND_CONVERSION_LUT.map(py, |lut| {
+            let conversion_function = lut.entry(type_pointer).or_insert_with(|| {
+                if ob.is_instance_of::<PyNodeAttributesTreeOperand>() {
+                    convert_py_node_attributes_tree_operand
+                } else if ob.is_instance_of::<PyEdgeAttributesTreeOperand>() {
+                    convert_py_edge_attributes_tree_operand
+                } else if ob.is_instance_of::<PyNodeMultipleAttributesOperand>() {
+                    convert_py_node_multiple_attributes_operand
+                } else if ob.is_instance_of::<PyEdgeMultipleAttributesOperand>() {
+                    convert_py_edge_multiple_attributes_operand
+                } else if ob.is_instance_of::<PyNodeSingleAttributeOperand>() {
+                    convert_py_node_single_attribute_operand
+                } else if ob.is_instance_of::<PyEdgeSingleAttributeOperand>() {
+                    convert_py_edge_single_attribute_operand
+                } else if ob.is_instance_of::<PyEdgeIndicesOperand>() {
+                    convert_py_edge_indices_operand
+                } else if ob.is_instance_of::<PyEdgeIndexOperand>() {
+                    convert_py_edge_index_operand
+                } else if ob.is_instance_of::<PyNodeIndicesOperand>() {
+                    convert_py_node_indices_operand
+                } else if ob.is_instance_of::<PyNodeIndexOperand>() {
+                    convert_py_node_index_operand
+                } else if ob.is_instance_of::<PyNodeMultipleValuesOperand>() {
+                    convert_py_node_multiple_values_operand
+                } else if ob.is_instance_of::<PyEdgeMultipleValuesOperand>() {
+                    convert_py_edge_multiple_values_operand
+                } else if ob.is_instance_of::<PyNodeSingleValueOperand>() {
+                    convert_py_node_single_value_operand
+                } else if ob.is_instance_of::<PyEdgeSingleValueOperand>() {
+                    convert_py_edge_single_value_operand
+                } else if ob.is_instance_of::<PyList>() {
+                    convert_py_list
+                } else {
+                    throw_error
+                }
+            });
+
+            conversion_function(ob)
+        })
+    })
 }
 
-impl<'a> From<PyReturnValue<'a>> for ReturnValue<'a> {
-    fn from(value: PyReturnValue<'a>) -> Self {
-        value.0
+impl FromPyObject<'_> for PyReturnOperand {
+    fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
+        convert_pyobject_to_pyreturnoperand(ob)
     }
 }
 
+pub enum PyReturnValue<'a> {
+    NodeAttributesTree(<Wrapper<NodeAttributesTreeOperand> as ReturnOperand<'a>>::ReturnValue),
+    EdgeAttributesTree(<Wrapper<EdgeAttributesTreeOperand> as ReturnOperand<'a>>::ReturnValue),
+    NodeMultipleAttributes(
+        <Wrapper<NodeMultipleAttributesOperand> as ReturnOperand<'a>>::ReturnValue,
+    ),
+    EdgeMultipleAttributes(
+        <Wrapper<EdgeMultipleAttributesOperand> as ReturnOperand<'a>>::ReturnValue,
+    ),
+    NodeSingleAttribute(<Wrapper<NodeSingleAttributeOperand> as ReturnOperand<'a>>::ReturnValue),
+    EdgeSingleAttribute(<Wrapper<EdgeSingleAttributeOperand> as ReturnOperand<'a>>::ReturnValue),
+    EdgeIndices(<Wrapper<EdgeIndicesOperand> as ReturnOperand<'a>>::ReturnValue),
+    EdgeIndex(<Wrapper<EdgeIndexOperand> as ReturnOperand<'a>>::ReturnValue),
+    NodeIndices(<Wrapper<NodeIndicesOperand> as ReturnOperand<'a>>::ReturnValue),
+    NodeIndex(<Wrapper<NodeIndexOperand> as ReturnOperand<'a>>::ReturnValue),
+    NodeMultipleValues(<Wrapper<NodeMultipleValuesOperand> as ReturnOperand<'a>>::ReturnValue),
+    EdgeMultipleValues(<Wrapper<EdgeMultipleValuesOperand> as ReturnOperand<'a>>::ReturnValue),
+    NodeSingleValue(<Wrapper<NodeSingleValueOperand> as ReturnOperand<'a>>::ReturnValue),
+    EdgeSingleValue(<Wrapper<EdgeSingleValueOperand> as ReturnOperand<'a>>::ReturnValue),
+    Vector(Vec<Self>),
+}
+
 impl<'py> IntoPyObject<'py> for PyReturnValue<'_> {
     type Target = PyAny;
     type Output = Bound<'py, Self::Target>;
     type Error = PyErr;
 
     fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
-        match self.0 {
-            ReturnValue::NodeAttributesTree(iterator) => iterator
+        match self {
+            PyReturnValue::NodeAttributesTree(iterator) => iterator
                 .map(|item| {
                     (
                         PyNodeIndex::from(item.0.clone()),
@@ -164,11 +347,11 @@ impl<'py> IntoPyObject<'py> for PyReturnValue<'_> {
                 })
                 .collect::<HashMap<_, _>>()
                 .into_bound_py_any(py),
-            ReturnValue::EdgeAttributesTree(iterator) => iterator
+            PyReturnValue::EdgeAttributesTree(iterator) => iterator
                 .map(|item| (item.0, Vec::<PyMedRecordAttribute>::deep_from(item.1)))
                 .collect::<HashMap<_, _>>()
                 .into_bound_py_any(py),
-            ReturnValue::NodeMultipleAttributes(iterator) => iterator
+            PyReturnValue::NodeMultipleAttributes(iterator) => iterator
                 .map(|item| {
                     (
                         PyNodeIndex::from(item.0.clone()),
@@ -177,11 +360,11 @@ impl<'py> IntoPyObject<'py> for PyReturnValue<'_> {
                 })
                 .collect::<HashMap<_, _>>()
                 .into_bound_py_any(py),
-            ReturnValue::EdgeMultipleAttributes(iterator) => iterator
+            PyReturnValue::EdgeMultipleAttributes(iterator) => iterator
                 .map(|item| (item.0, PyMedRecordAttribute::from(item.1)))
                 .collect::<HashMap<_, _>>()
                 .into_bound_py_any(py),
-            ReturnValue::NodeSingleAttribute(attribute) => match attribute {
+            PyReturnValue::NodeSingleAttribute(attribute) => match attribute {
                 Some(attribute) => match attribute {
                     OptionalIndexWrapper::WithIndex((index, attribute)) => (
                         PyNodeIndex::from(index.clone()),
@@ -194,7 +377,7 @@ impl<'py> IntoPyObject<'py> for PyReturnValue<'_> {
                 },
                 None => PyNone::get(py).into_bound_py_any(py),
             },
-            ReturnValue::EdgeSingleAttribute(attribute) => match attribute {
+            PyReturnValue::EdgeSingleAttribute(attribute) => match attribute {
                 Some(attribute) => match attribute {
                     OptionalIndexWrapper::WithIndex((index, attribute)) => {
                         (index, PyMedRecordAttribute::from(attribute)).into_bound_py_any(py)
@@ -205,18 +388,18 @@ impl<'py> IntoPyObject<'py> for PyReturnValue<'_> {
                 },
                 None => PyNone::get(py).into_bound_py_any(py),
             },
-            ReturnValue::EdgeIndices(iterator) => {
+            PyReturnValue::EdgeIndices(iterator) => {
                 iterator.collect::<Vec<_>>().into_bound_py_any(py)
             }
-            ReturnValue::EdgeIndex(index) => index.into_bound_py_any(py),
-            ReturnValue::NodeIndices(iterator) => iterator
+            PyReturnValue::EdgeIndex(index) => index.into_bound_py_any(py),
+            PyReturnValue::NodeIndices(iterator) => iterator
                 .map(PyMedRecordAttribute::from)
                 .collect::<Vec<_>>()
                 .into_bound_py_any(py),
-            ReturnValue::NodeIndex(index) => {
+            PyReturnValue::NodeIndex(index) => {
                 Option::<PyNodeIndex>::deep_from(index).into_bound_py_any(py)
             }
-            ReturnValue::NodeMultipleValues(iterator) => iterator
+            PyReturnValue::NodeMultipleValues(iterator) => iterator
                 .map(|item| {
                     (
                         PyNodeIndex::from(item.0.clone()),
@@ -225,11 +408,11 @@ impl<'py> IntoPyObject<'py> for PyReturnValue<'_> {
                 })
                 .collect::<HashMap<_, _>>()
                 .into_bound_py_any(py),
-            ReturnValue::EdgeMultipleValues(iterator) => iterator
+            PyReturnValue::EdgeMultipleValues(iterator) => iterator
                 .map(|item| (item.0, PyMedRecordValue::from(item.1)))
                 .collect::<HashMap<_, _>>()
                 .into_bound_py_any(py),
-            ReturnValue::NodeSingleValue(value) => match value {
+            PyReturnValue::NodeSingleValue(value) => match value {
                 Some(value) => match value {
                     OptionalIndexWrapper::WithIndex((index, value)) => (
                         PyNodeIndex::from(index.clone()),
@@ -242,7 +425,7 @@ impl<'py> IntoPyObject<'py> for PyReturnValue<'_> {
                 },
                 None => PyNone::get(py).into_bound_py_any(py),
             },
-            ReturnValue::EdgeSingleValue(value) => match value {
+            PyReturnValue::EdgeSingleValue(value) => match value {
                 Some(value) => match value {
                     OptionalIndexWrapper::WithIndex((index, value)) => {
                         (index, PyMedRecordValue::from(value)).into_bound_py_any(py)
@@ -253,6 +436,7 @@ impl<'py> IntoPyObject<'py> for PyReturnValue<'_> {
                 },
                 None => PyNone::get(py).into_bound_py_any(py),
             },
+            PyReturnValue::Vector(vector) => vector.into_bound_py_any(py),
         }
     }
 }
diff --git a/rustmodels/src/medrecord/value.rs b/rustmodels/src/medrecord/value.rs
index e13e0b83..fc4be045 100644
--- a/rustmodels/src/medrecord/value.rs
+++ b/rustmodels/src/medrecord/value.rs
@@ -50,27 +50,40 @@ pub(crate) fn convert_pyobject_to_medrecordvalue(
     ob: &Bound<'_, PyAny>,
 ) -> PyResult<MedRecordValue> {
     fn convert_string(ob: &Bound<'_, PyAny>) -> PyResult<MedRecordValue> {
-        Ok(MedRecordValue::String(ob.extract::<String>()?))
+        Ok(MedRecordValue::String(
+            ob.extract::<String>().expect("Extraction must succeed"),
+        ))
     }
 
     fn convert_int(ob: &Bound<'_, PyAny>) -> PyResult<MedRecordValue> {
-        Ok(MedRecordValue::Int(ob.extract::<i64>()?))
+        Ok(MedRecordValue::Int(
+            ob.extract::<i64>().expect("Extraction must succeed"),
+        ))
     }
 
     fn convert_float(ob: &Bound<'_, PyAny>) -> PyResult<MedRecordValue> {
-        Ok(MedRecordValue::Float(ob.extract::<f64>()?))
+        Ok(MedRecordValue::Float(
+            ob.extract::<f64>().expect("Extraction must succeed"),
+        ))
     }
 
     fn convert_bool(ob: &Bound<'_, PyAny>) -> PyResult<MedRecordValue> {
-        Ok(MedRecordValue::Bool(ob.extract::<bool>()?))
+        Ok(MedRecordValue::Bool(
+            ob.extract::<bool>().expect("Extraction must succeed"),
+        ))
     }
 
     fn convert_datetime(ob: &Bound<'_, PyAny>) -> PyResult<MedRecordValue> {
-        Ok(MedRecordValue::DateTime(ob.extract::<NaiveDateTime>()?))
+        Ok(MedRecordValue::DateTime(
+            ob.extract::<NaiveDateTime>()
+                .expect("Extraction must succeed"),
+        ))
     }
 
     fn convert_duration(ob: &Bound<'_, PyAny>) -> PyResult<MedRecordValue> {
-        Ok(MedRecordValue::Duration(ob.extract::<Duration>()?))
+        Ok(MedRecordValue::Duration(
+            ob.extract::<Duration>().expect("Extraction must succeed"),
+        ))
     }
 
     fn convert_null(_ob: &Bound<'_, PyAny>) -> PyResult<MedRecordValue> {
diff --git a/tests/medrecord/test_medrecord.py b/tests/medrecord/test_medrecord.py
index eebc402e..8b47f627 100644
--- a/tests/medrecord/test_medrecord.py
+++ b/tests/medrecord/test_medrecord.py
@@ -28,9 +28,15 @@
     NodeOperand,
     NodeSingleAttributeOperand,
     NodeSingleValueOperand,
+    QueryReturnOperand,
 )
 from medmodels.medrecord.schema import AttributeType, GroupSchema, Schema, SchemaType
-from medmodels.medrecord.types import AttributesInput, NodeIndex
+from medmodels.medrecord.types import (
+    AttributesInput,
+    NodeIndex,
+    is_edge_index_list,
+    is_node_index_list,
+)
 
 
 # TODO(#397): Change AttributesInput to Attributes
@@ -2311,6 +2317,15 @@ def query9(node: NodeOperand) -> EdgeIndicesOperand:
 
         assert sorted(medrecord.query_nodes(query9)) == [0, 1, 3]
 
+        def query10(node: NodeOperand) -> List[QueryReturnOperand]:
+            node.index().equal_to("0")
+            return [node.index(), node.edges().index()]
+
+        node_indices, edge_indices = medrecord.query_nodes(query10)
+        assert node_indices == ["0"]
+        assert is_edge_index_list(edge_indices)
+        assert sorted(edge_indices) == [0, 1, 3]
+
     def test_query_edges(self) -> None:
         medrecord = create_medrecord()
 
@@ -2370,6 +2385,15 @@ def query9(edge: EdgeOperand) -> NodeIndicesOperand:
 
         assert sorted(medrecord.query_edges(query9)) == ["0", "0", "1", "1"]
 
+        def query10(edge: EdgeOperand) -> List[QueryReturnOperand]:
+            edge.index().equal_to(0)
+            return [edge.index(), edge.source_node().index()]
+
+        edge_indices, node_indices = medrecord.query_edges(query10)
+        assert edge_indices == [0]
+        assert is_node_index_list(node_indices)
+        assert sorted(node_indices) == ["0"]
+
     def test_describe_group_nodes(self) -> None:
         medrecord = create_medrecord()
 
diff --git a/tests/medrecord/test_querying.py b/tests/medrecord/test_querying.py
index e71c4da2..16d0c490 100644
--- a/tests/medrecord/test_querying.py
+++ b/tests/medrecord/test_querying.py
@@ -677,7 +677,7 @@ def query8(node: NodeOperand) -> NodeIndicesOperand:
 
         assert self.medrecord.query_nodes(query8) == ["pat_10"]
 
-    def test_node_multiple_values_operand_comparisons(self) -> None:  # noqa: C901
+    def test_node_multiple_values_operand_comparisons(self) -> None:
         def query1(node: NodeOperand) -> NodeIndicesOperand:
             node.attribute("age").is_max()
             return node.index()
@@ -770,7 +770,7 @@ def query13(node: NodeOperand) -> NodeIndicesOperand:
             "pat_5",
         ]
 
-    def test_node_multiple_values_operand_operations(self) -> None:  # noqa: C901  # noqa: C901
+    def test_node_multiple_values_operand_operations(self) -> None:
         def query1(node: NodeOperand) -> NodeMultipleValuesOperand:
             query_node(node)
             age = node.attribute("age")
@@ -972,7 +972,7 @@ def setUp(self) -> None:
             ]
         )
 
-    def test_edge_multiple_values_operand_numeric(self) -> None:  # noqa: C901
+    def test_edge_multiple_values_operand_numeric(self) -> None:
         def query_min(edge: EdgeOperand) -> EdgeSingleValueOperand:
             query_specific_edge(edge, 4)
             attribute = edge.attribute("duration_days")
@@ -1098,7 +1098,7 @@ def query8(edge: EdgeOperand) -> EdgeIndicesOperand:
 
         assert self.medrecord.query_edges(query8) == [162]
 
-    def test_edge_multiple_values_operand_comparisons(self) -> None:  # noqa: C901
+    def test_edge_multiple_values_operand_comparisons(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeIndicesOperand:
             attribute = edge.attribute("duration_days")
             attribute.is_float()
@@ -1190,7 +1190,7 @@ def query13(edge: EdgeOperand) -> EdgeIndicesOperand:
 
         assert self.medrecord.query_edges(query13) == [160]
 
-    def test_edge_multiple_values_operand_operations(self) -> None:  # noqa: C901
+    def test_edge_multiple_values_operand_operations(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeMultipleValuesOperand:
             query_specific_edge(edge, 3)
             duration = edge.attribute("duration_days")
@@ -1455,7 +1455,7 @@ def query8(node: NodeOperand) -> NodeIndicesOperand:
 
         assert self.medrecord.query_nodes(query8) == ["pat_10"]
 
-    def test_node_single_value_operand_comparisons(self) -> None:  # noqa: C901
+    def test_node_single_value_operand_comparisons(self) -> None:
         def query1(node: NodeOperand) -> NodeSingleValueOperand:
             maximum = node.attribute("age").max()
             maximum.greater_than(90)
@@ -1533,7 +1533,7 @@ def query11(node: NodeOperand) -> NodeSingleValueOperand:
 
         assert self.medrecord.query_nodes(query11) == ("pat_3", 96)
 
-    def test_node_single_value_operand_operations(self) -> None:  # noqa: C901
+    def test_node_single_value_operand_operations(self) -> None:
         def query1(node: NodeOperand) -> NodeSingleValueOperand:
             maximum = node.attribute("age").max()
             maximum.add(10)
@@ -1783,7 +1783,7 @@ def query8(edge: EdgeOperand) -> EdgeSingleValueOperand:
 
         assert self.medrecord.query_edges(query8) == (47, 3416)
 
-    def test_edge_single_value_operand_comparisons(self) -> None:  # noqa: C901
+    def test_edge_single_value_operand_comparisons(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeSingleValueOperand:
             value = edge.attribute("integer_attribute").max()
             value.greater_than(4)
@@ -1861,7 +1861,7 @@ def query11(edge: EdgeOperand) -> EdgeSingleValueOperand:
 
         assert self.medrecord.query_edges(query11) == (160, " Hello ")
 
-    def test_edge_single_value_operand_operations(self) -> None:  # noqa: C901
+    def test_edge_single_value_operand_operations(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeSingleValueOperand:
             value = edge.attribute("integer_attribute").max()
             value.add(10)
@@ -2042,7 +2042,7 @@ class TestNodeIndicesOperand(unittest.TestCase):
     def setUp(self) -> None:
         self.medrecord = MedRecord.from_simple_example_dataset()
 
-    def test_node_indices_operand_comparisons(self) -> None:  # noqa: C901
+    def test_node_indices_operand_comparisons(self) -> None:
         def query1(node: NodeOperand) -> NodeIndicesOperand:
             node.index().equal_to("pat_1")
             return node.index()
@@ -2161,7 +2161,7 @@ def query15(node: NodeOperand) -> NodeIndicesOperand:
 
         assert self.medrecord.query_nodes(query15) == [0]
 
-    def test_node_indices_operand_operations(self) -> None:  # noqa: C901
+    def test_node_indices_operand_operations(self) -> None:
         self.medrecord.unfreeze_schema()
         self.medrecord.add_nodes((10, {}))
         self.medrecord.add_nodes((11, {}))
@@ -2328,7 +2328,7 @@ class TestNodeIndexOperand(unittest.TestCase):
     def setUp(self) -> None:
         self.medrecord = MedRecord.from_simple_example_dataset()
 
-    def test_node_index_operand_comparisons(self) -> None:  # noqa: C901
+    def test_node_index_operand_comparisons(self) -> None:
         def query1(node: NodeOperand) -> NodeIndexOperand:
             node.in_group("patient")
             maximum = node.index().max()
@@ -2437,7 +2437,7 @@ def query13(node: NodeOperand) -> NodeIndexOperand:
 
         assert self.medrecord.query_nodes(query13) == 1
 
-    def test_node_index_operand_operations(self) -> None:  # noqa: C901
+    def test_node_index_operand_operations(self) -> None:
         self.medrecord.unfreeze_schema()
         self.medrecord.add_nodes((10, {}))
         self.medrecord.add_nodes((11, {}))
@@ -2576,7 +2576,7 @@ class TestEdgeIndicesOperand(unittest.TestCase):
     def setUp(self) -> None:
         self.medrecord = MedRecord.from_simple_example_dataset()
 
-    def test_edge_indices_operand_comparisons(self) -> None:  # noqa: C901
+    def test_edge_indices_operand_comparisons(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeIndicesOperand:
             edge.index().equal_to(0)
             return edge.index()
@@ -2665,7 +2665,7 @@ def query13(edge: EdgeOperand) -> EdgeIndicesOperand:
 
         assert self.medrecord.query_edges(query13) == [0]
 
-    def test_edge_indices_operand_operations(self) -> None:  # noqa: C901
+    def test_edge_indices_operand_operations(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeIndexOperand:
             edge.index().is_in([10, 11])
             return edge.index().max()
@@ -2764,7 +2764,7 @@ class TestEdgeIndexOperand(unittest.TestCase):
     def setUp(self) -> None:
         self.medrecord = MedRecord.from_simple_example_dataset()
 
-    def test_edge_index_operand_comparisons(self) -> None:  # noqa: C901
+    def test_edge_index_operand_comparisons(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeIndexOperand:
             minimum = edge.index().min()
             minimum.equal_to(0)
@@ -2914,7 +2914,7 @@ class TestNodeAttributesTreeOperand(unittest.TestCase):
     def setUp(self) -> None:
         self.medrecord = MedRecord.from_simple_example_dataset()
 
-    def test_node_attributes_tree_operand_comparisons(self) -> None:  # noqa: C901
+    def test_node_attributes_tree_operand_comparisons(self) -> None:
         def query1(node: NodeOperand) -> NodeMultipleAttributesOperand:
             query_node(node)
             return node.attributes().max()
@@ -3082,7 +3082,7 @@ def query20(node: NodeOperand) -> NodeAttributesTreeOperand:
 
         assert self.medrecord.query_nodes(query20) == {0: [2]}
 
-    def test_node_attributes_tree_operand_operations(self) -> None:  # noqa: C901
+    def test_node_attributes_tree_operand_operations(self) -> None:
         self.medrecord.unfreeze_schema()
         self.medrecord.add_nodes((0, {10: "value1", 11: "value2"}))
 
@@ -3251,7 +3251,7 @@ def setUp(self) -> None:
             [("pat_4", "pat_1", {"a_attribute": "v5", "b_attribute": "v6"})]
         )  # Edge index 164
 
-    def test_edge_attributes_tree_operand_comparisons(self) -> None:  # noqa: C901
+    def test_edge_attributes_tree_operand_comparisons(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeMultipleAttributesOperand:
             query_specific_edge(edge, 164)
             return edge.attributes().max()
@@ -3415,7 +3415,7 @@ def query20(edge: EdgeOperand) -> EdgeAttributesTreeOperand:
 
         assert self.medrecord.query_edges(query20) == {160: [2]}
 
-    def test_edge_attributes_tree_operand_operations(self) -> None:  # noqa: C901
+    def test_edge_attributes_tree_operand_operations(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeAttributesTreeOperand:
             query_specific_edge(edge, 161)
             attributes = edge.attributes()
@@ -3564,7 +3564,7 @@ class TestNodeMultipleAttributesOperand(unittest.TestCase):
     def setUp(self) -> None:
         self.medrecord = MedRecord.from_simple_example_dataset()
 
-    def test_node_multiple_attributes_operand_comparisons(self) -> None:  # noqa: C901
+    def test_node_multiple_attributes_operand_comparisons(self) -> None:
         self.medrecord.unfreeze_schema()
         self.medrecord.add_nodes((0, {10: "value1", 11: "value2"}))
         self.medrecord.add_nodes((1, {12: "value3", 13: "value4"}))
@@ -3721,7 +3721,7 @@ def query20(node: NodeOperand) -> NodeMultipleAttributesOperand:
 
         assert self.medrecord.query_nodes(query20) == {"pat_1": "gender"}
 
-    def test_node_multiple_attributes_operand_operations(self) -> None:  # noqa: C901
+    def test_node_multiple_attributes_operand_operations(self) -> None:
         self.medrecord.unfreeze_schema()
         self.medrecord.add_nodes((0, {10: "value1", 11: "value2"}))
         self.medrecord.add_nodes((1, {12: "value3", 13: "value4"}))
@@ -3876,7 +3876,7 @@ def setUp(self) -> None:
         self.medrecord.add_edges([("pat_3", "pat_4", {12: "value3", 13: "value4"})])
         self.medrecord.add_edges([("pat_5", "pat_1", {" Hello ": "value5"})])
 
-    def test_edge_multiple_attributes_operand_comparisons(self) -> None:  # noqa: C901
+    def test_edge_multiple_attributes_operand_comparisons(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeSingleAttributeOperand:
             edge.index().is_in([160, 161])
             return edge.attributes().max().max()
@@ -4029,7 +4029,7 @@ def query20(edge: EdgeOperand) -> EdgeMultipleAttributesOperand:
 
         assert self.medrecord.query_edges(query20) == {0: "time"}
 
-    def test_edge_multiple_attributes_operand_operations(self) -> None:  # noqa: C901
+    def test_edge_multiple_attributes_operand_operations(self) -> None:
         def query1(edge: EdgeOperand) -> EdgeMultipleAttributesOperand:
             edge.index().is_in([160, 161])
             attribute = edge.attributes().max()
@@ -4173,7 +4173,7 @@ class TestNodeSingleAttributeOperand(unittest.TestCase):
     def setUp(self) -> None:
         self.medrecord = MedRecord.from_simple_example_dataset()
 
-    def test_single_attribute_operand_comparisons(self) -> None:  # noqa: C901
+    def test_single_attribute_operand_comparisons(self) -> None:
         self.medrecord.unfreeze_schema()
         self.medrecord.add_nodes((0, {10: "value1", 11: "value2"}))
         self.medrecord.add_nodes((1, {12: "value3", 13: "value4"}))
@@ -4425,7 +4425,7 @@ def setUp(self) -> None:
         self.medrecord.add_edges([("pat_3", "pat_4", {12: "value3", 13: "value4"})])
         self.medrecord.add_edges([("pat_5", "pat_1", {" Hello ": "value5"})])
 
-    def test_single_attribute_operand_comparisons(self) -> None:  # noqa: C901
+    def test_single_attribute_operand_comparisons(self) -> None:
         self.medrecord.unfreeze_schema()
 
         def query1(edge: EdgeOperand) -> EdgeSingleAttributeOperand:
diff --git a/tests/medrecord/test_types.py b/tests/medrecord/test_types.py
index ed1e438d..65c25365 100644
--- a/tests/medrecord/test_types.py
+++ b/tests/medrecord/test_types.py
@@ -7,12 +7,14 @@
 from medmodels.medrecord.types import (
     is_attributes,
     is_edge_index,
+    is_edge_index_list,
     is_edge_tuple,
     is_edge_tuple_list,
     is_group,
     is_medrecord_attribute,
     is_medrecord_value,
     is_node_index,
+    is_node_index_list,
     is_node_tuple,
     is_node_tuple_list,
     is_pandas_edge_dataframe_input,
@@ -48,11 +50,24 @@ def test_is_node_index(self) -> None:
         assert is_node_index(123)
         assert not is_node_index(12.34)
 
+    def test_is_node_index_list(self) -> None:
+        assert is_node_index_list(["node1", "node2"])
+        assert is_node_index_list([123, 456])
+        assert is_node_index_list(["node1", 123])
+        assert not is_node_index_list("invalid")
+        assert not is_node_index_list([123, 12.34])
+
     def test_is_edge_index(self) -> None:
         assert is_edge_index(123)
         assert not is_edge_index("edge")
         assert not is_edge_index(12.34)
 
+    def test_is_edge_index_list(self) -> None:
+        assert is_edge_index_list([123, 456])
+        assert not is_edge_index_list("invalid")
+        assert not is_edge_index_list([123, "edge"])
+        assert not is_edge_index_list([123, 12.34])
+
     def test_is_group(self) -> None:
         assert is_group("group")
         assert is_group(123)


Reviews:
user: login: LauraBoenchenLB
body: ![Gemini_Generated_Image_4msb674msb674msb](https://github.com/user-attachments/assets/20d24907-c19e-4b7c-bfb5-bbcc09f01696)

not much to complain about, would be fine merging it! :)
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T13:54:10Z
user: login: JabobKrauskopf
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T14:10:26Z
user: login: LauraBoenchenLB
body: Perfection ✨ 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T14:12:11Z
user: login: MarIniOnz
body: Almost approved!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T14:16:36Z
user: login: JabobKrauskopf
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T14:22:01Z
user: login: JabobKrauskopf
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T14:24:07Z
user: login: MarIniOnz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T14:25:37Z
user: login: JabobKrauskopf
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T14:27:50Z
user: login: MarIniOnz
body: Approveeed!

PS: Navy SEAL seal *Returning* home

![ChatGPT Image May 14, 2025, 04_28_58 PM](https://github.com/user-attachments/assets/40c568b8-ebe6-4c74-8daa-a69c47aa91af)

state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T14:30:03Z

Review comments:
diff_hunk: @@ -81,7 +81,9 @@ pub(crate) fn convert_pyobject_to_datatype(ob: &Bound<'_, pyo3::PyAny>) -> PyRes
     }
 
     fn convert_union(ob: &Bound<'_, pyo3::PyAny>) -> PyResult<DataType> {
-        let union = ob.extract::<PyRef<PyUnion>>()?;
+        let union = ob
+            .extract::<PyRef<PyUnion>>()
+            .expect("Extraction must succeed");
path: rustmodels/src/medrecord/datatype.rs
user: login: LauraBoenchenLB
body: sounds like a Scifi Villain 
created_at: 2025-05-14T13:47:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -706,11 +706,12 @@ impl PyMedRecord {
                     .call1((PyNodeOperand::from(node.clone()),))
                     .expect("Call must succeed");
 
-                parse_query_result(result)
+                result
+                    .extract::<PyReturnOperand>()
+                    .expect("Extract must succeed")
path: rustmodels/src/medrecord/mod.rs
user: login: LauraBoenchenLB
body: Here also `Extraction`? 
created_at: 2025-05-14T13:49:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -706,11 +706,12 @@ impl PyMedRecord {
                     .call1((PyNodeOperand::from(node.clone()),))
                     .expect("Call must succeed");
 
-                parse_query_result(result)
+                result
+                    .extract::<PyReturnOperand>()
+                    .expect("Extract must succeed")
path: rustmodels/src/medrecord/mod.rs
user: login: JabobKrauskopf
body: Merci!
created_at: 2025-05-14T14:10:26Z
author_association: MEMBER
diff_hunk: @@ -101,203 +146,78 @@ impl<I: Index, T> From<(I, T)> for OptionalIndexWrapper<I, T> {
 }
 
 #[derive(Debug, Clone)]
-pub struct Selection<'a> {
+pub struct Selection<'a, R: ReturnOperand<'a>> {
     medrecord: &'a MedRecord,
-    return_operand: ReturnOperand,
+    return_operand: R,
 }
 
-impl<'a> Selection<'a> {
-    pub fn new_node<Q, R>(medrecord: &'a MedRecord, query: Q) -> Self
+impl<'a, R: ReturnOperand<'a>> Selection<'a, R> {
+    pub fn new_node<Q>(medrecord: &'a MedRecord, query: Q) -> Self
     where
         Q: FnOnce(&mut Wrapper<NodeOperand>) -> R,
-        R: Into<ReturnOperand>,
     {
         let mut operand = Wrapper::<NodeOperand>::new(None);
 
         Self {
             medrecord,
-            return_operand: query(&mut operand).into(),
+            return_operand: query(&mut operand),
         }
     }
 
-    pub fn new_edge<Q, R>(medrecord: &'a MedRecord, query: Q) -> Self
+    pub fn new_edge<Q>(medrecord: &'a MedRecord, query: Q) -> Self
     where
         Q: FnOnce(&mut Wrapper<EdgeOperand>) -> R,
-        R: Into<ReturnOperand>,
     {
         let mut operand = Wrapper::<EdgeOperand>::new(None);
 
         Self {
             medrecord,
-            return_operand: query(&mut operand).into(),
+            return_operand: query(&mut operand),
         }
     }
 
-    pub fn evaluate(self) -> MedRecordResult<ReturnValue<'a>> {
-        let result = match self.return_operand {
-            ReturnOperand::NodeAttributesTree(operand) => ReturnValue::NodeAttributesTree(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeAttributesTree(operand) => ReturnValue::EdgeAttributesTree(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeMultipleAttributes(operand) => ReturnValue::NodeMultipleAttributes(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeMultipleAttributes(operand) => ReturnValue::EdgeMultipleAttributes(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeSingleAttribute(operand) => {
-                ReturnValue::NodeSingleAttribute(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeSingleAttribute(operand) => {
-                ReturnValue::EdgeSingleAttribute(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeIndices(operand) => {
-                ReturnValue::EdgeIndices(Box::new(operand.evaluate_backward(self.medrecord)?))
-            }
-            ReturnOperand::EdgeIndex(operand) => {
-                ReturnValue::EdgeIndex(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::NodeIndices(operand) => {
-                ReturnValue::NodeIndices(Box::new(operand.evaluate_backward(self.medrecord)?))
-            }
-            ReturnOperand::NodeIndex(operand) => {
-                ReturnValue::NodeIndex(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::NodeMultipleValues(operand) => ReturnValue::NodeMultipleValues(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeMultipleValues(operand) => ReturnValue::EdgeMultipleValues(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeSingleValue(operand) => {
-                ReturnValue::NodeSingleValue(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeSingleValue(operand) => {
-                ReturnValue::EdgeSingleValue(operand.evaluate_backward(self.medrecord)?)
-            }
-        };
-
-        Ok(result)
-    }
-}
-
-#[derive(Debug, Clone)]
-pub enum ReturnOperand {
-    NodeAttributesTree(Wrapper<NodeAttributesTreeOperand>),
-    EdgeAttributesTree(Wrapper<EdgeAttributesTreeOperand>),
-    NodeMultipleAttributes(Wrapper<NodeMultipleAttributesOperand>),
-    EdgeMultipleAttributes(Wrapper<EdgeMultipleAttributesOperand>),
-    NodeSingleAttribute(Wrapper<NodeSingleAttributeOperand>),
-    EdgeSingleAttribute(Wrapper<EdgeSingleAttributeOperand>),
-    EdgeIndices(Wrapper<EdgeIndicesOperand>),
-    EdgeIndex(Wrapper<EdgeIndexOperand>),
-    NodeIndices(Wrapper<NodeIndicesOperand>),
-    NodeIndex(Wrapper<NodeIndexOperand>),
-    NodeMultipleValues(Wrapper<NodeMultipleValuesOperand>),
-    EdgeMultipleValues(Wrapper<EdgeMultipleValuesOperand>),
-    NodeSingleValue(Wrapper<NodeSingleValueOperand>),
-    EdgeSingleValue(Wrapper<EdgeSingleValueOperand>),
-}
-
-impl From<Wrapper<NodeAttributesTreeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeAttributesTreeOperand>) -> Self {
-        Self::NodeAttributesTree(operand)
-    }
-}
-
-impl From<Wrapper<EdgeAttributesTreeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeAttributesTreeOperand>) -> Self {
-        Self::EdgeAttributesTree(operand)
-    }
-}
-
-impl From<Wrapper<NodeMultipleAttributesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeMultipleAttributesOperand>) -> Self {
-        Self::NodeMultipleAttributes(operand)
-    }
-}
-
-impl From<Wrapper<EdgeMultipleAttributesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeMultipleAttributesOperand>) -> Self {
-        Self::EdgeMultipleAttributes(operand)
-    }
-}
-
-impl From<Wrapper<NodeSingleAttributeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeSingleAttributeOperand>) -> Self {
-        Self::NodeSingleAttribute(operand)
-    }
-}
-
-impl From<Wrapper<EdgeSingleAttributeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeSingleAttributeOperand>) -> Self {
-        Self::EdgeSingleAttribute(operand)
-    }
-}
-
-impl From<Wrapper<EdgeIndicesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeIndicesOperand>) -> Self {
-        Self::EdgeIndices(operand)
-    }
-}
-
-impl From<Wrapper<EdgeIndexOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeIndexOperand>) -> Self {
-        Self::EdgeIndex(operand)
-    }
-}
-
-impl From<Wrapper<NodeIndicesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeIndicesOperand>) -> Self {
-        Self::NodeIndices(operand)
-    }
-}
-
-impl From<Wrapper<NodeIndexOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeIndexOperand>) -> Self {
-        Self::NodeIndex(operand)
-    }
-}
-
-impl From<Wrapper<NodeMultipleValuesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeMultipleValuesOperand>) -> Self {
-        Self::NodeMultipleValues(operand)
-    }
-}
-
-impl From<Wrapper<EdgeMultipleValuesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeMultipleValuesOperand>) -> Self {
-        Self::EdgeMultipleValues(operand)
-    }
-}
-
-impl From<Wrapper<NodeSingleValueOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeSingleValueOperand>) -> Self {
-        Self::NodeSingleValue(operand)
-    }
-}
-
-impl From<Wrapper<EdgeSingleValueOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeSingleValueOperand>) -> Self {
-        Self::EdgeSingleValue(operand)
-    }
-}
-
-pub enum ReturnValue<'a> {
-    NodeAttributesTree(Box<dyn Iterator<Item = (&'a NodeIndex, Vec<MedRecordAttribute>)> + 'a>),
-    EdgeAttributesTree(Box<dyn Iterator<Item = (&'a EdgeIndex, Vec<MedRecordAttribute>)> + 'a>),
-    NodeMultipleAttributes(Box<dyn Iterator<Item = (&'a NodeIndex, MedRecordAttribute)> + 'a>),
-    EdgeMultipleAttributes(Box<dyn Iterator<Item = (&'a EdgeIndex, MedRecordAttribute)> + 'a>),
-    NodeSingleAttribute(Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordAttribute>>),
-    EdgeSingleAttribute(Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordAttribute>>),
-    EdgeIndices(Box<dyn Iterator<Item = EdgeIndex> + 'a>),
-    EdgeIndex(Option<EdgeIndex>),
-    NodeIndices(Box<dyn Iterator<Item = NodeIndex> + 'a>),
-    NodeIndex(Option<NodeIndex>),
-    NodeMultipleValues(Box<dyn Iterator<Item = (&'a NodeIndex, MedRecordValue)> + 'a>),
-    EdgeMultipleValues(Box<dyn Iterator<Item = (&'a EdgeIndex, MedRecordValue)> + 'a>),
-    NodeSingleValue(Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordValue>>),
-    EdgeSingleValue(Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordValue>>),
-}
+    pub fn evaluate(self) -> MedRecordResult<R::ReturnValue> {
+        self.return_operand.evaluate(self.medrecord)
+    }
+}
+
+pub trait ReturnOperand<'a> {
+    type ReturnValue;
+
+    fn evaluate(self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue>;
+}
+
+impl_iterator_return_operand!(
+    NodeAttributesTreeOperand     => (&'a NodeIndex, Vec<MedRecordAttribute>),
+    EdgeAttributesTreeOperand     => (&'a EdgeIndex, Vec<MedRecordAttribute>),
+    NodeMultipleAttributesOperand => (&'a NodeIndex, MedRecordAttribute),
+    EdgeMultipleAttributesOperand => (&'a EdgeIndex, MedRecordAttribute),
+    EdgeIndicesOperand            => EdgeIndex,
+    NodeIndicesOperand            => NodeIndex,
+    NodeMultipleValuesOperand     => (&'a NodeIndex, MedRecordValue),
+    EdgeMultipleValuesOperand     => (&'a EdgeIndex, MedRecordValue),
+);
+
+impl_direct_return_operand!(
+    NodeSingleAttributeOperand => Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordAttribute>>,
+    EdgeSingleAttributeOperand => Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordAttribute>>,
+    EdgeIndexOperand           => Option<EdgeIndex>,
+    NodeIndexOperand           => Option<NodeIndex>,
+    NodeSingleValueOperand     => Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordValue>>,
+    EdgeSingleValueOperand     => Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordValue>>,
+);
+
+impl_return_operand_for_tuples!(R1, R2);
+impl_return_operand_for_tuples!(R1, R2, R3);
+impl_return_operand_for_tuples!(R1, R2, R3, R4);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15);
path: crates/medmodels-core/src/medrecord/querying/mod.rs
user: login: MarIniOnz
body: why up to 15? what are these R for 
created_at: 2025-05-14T14:12:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,24 +3,32 @@ pub mod edges;
 pub mod nodes;
 pub mod values;
 
+use crate::gil_hash_map::GILHashMap;
+
 use super::{
     attribute::PyMedRecordAttribute, errors::PyMedRecordError, traits::DeepFrom,
-    value::PyMedRecordValue, PyNodeIndex,
+    value::PyMedRecordValue, Lut, PyNodeIndex,
path: rustmodels/src/medrecord/querying/mod.rs
user: login: MarIniOnz
body: Lut?
created_at: 2025-05-14T14:15:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -101,203 +146,78 @@ impl<I: Index, T> From<(I, T)> for OptionalIndexWrapper<I, T> {
 }
 
 #[derive(Debug, Clone)]
-pub struct Selection<'a> {
+pub struct Selection<'a, R: ReturnOperand<'a>> {
     medrecord: &'a MedRecord,
-    return_operand: ReturnOperand,
+    return_operand: R,
 }
 
-impl<'a> Selection<'a> {
-    pub fn new_node<Q, R>(medrecord: &'a MedRecord, query: Q) -> Self
+impl<'a, R: ReturnOperand<'a>> Selection<'a, R> {
+    pub fn new_node<Q>(medrecord: &'a MedRecord, query: Q) -> Self
     where
         Q: FnOnce(&mut Wrapper<NodeOperand>) -> R,
-        R: Into<ReturnOperand>,
     {
         let mut operand = Wrapper::<NodeOperand>::new(None);
 
         Self {
             medrecord,
-            return_operand: query(&mut operand).into(),
+            return_operand: query(&mut operand),
         }
     }
 
-    pub fn new_edge<Q, R>(medrecord: &'a MedRecord, query: Q) -> Self
+    pub fn new_edge<Q>(medrecord: &'a MedRecord, query: Q) -> Self
     where
         Q: FnOnce(&mut Wrapper<EdgeOperand>) -> R,
-        R: Into<ReturnOperand>,
     {
         let mut operand = Wrapper::<EdgeOperand>::new(None);
 
         Self {
             medrecord,
-            return_operand: query(&mut operand).into(),
+            return_operand: query(&mut operand),
         }
     }
 
-    pub fn evaluate(self) -> MedRecordResult<ReturnValue<'a>> {
-        let result = match self.return_operand {
-            ReturnOperand::NodeAttributesTree(operand) => ReturnValue::NodeAttributesTree(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeAttributesTree(operand) => ReturnValue::EdgeAttributesTree(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeMultipleAttributes(operand) => ReturnValue::NodeMultipleAttributes(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeMultipleAttributes(operand) => ReturnValue::EdgeMultipleAttributes(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeSingleAttribute(operand) => {
-                ReturnValue::NodeSingleAttribute(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeSingleAttribute(operand) => {
-                ReturnValue::EdgeSingleAttribute(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeIndices(operand) => {
-                ReturnValue::EdgeIndices(Box::new(operand.evaluate_backward(self.medrecord)?))
-            }
-            ReturnOperand::EdgeIndex(operand) => {
-                ReturnValue::EdgeIndex(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::NodeIndices(operand) => {
-                ReturnValue::NodeIndices(Box::new(operand.evaluate_backward(self.medrecord)?))
-            }
-            ReturnOperand::NodeIndex(operand) => {
-                ReturnValue::NodeIndex(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::NodeMultipleValues(operand) => ReturnValue::NodeMultipleValues(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeMultipleValues(operand) => ReturnValue::EdgeMultipleValues(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeSingleValue(operand) => {
-                ReturnValue::NodeSingleValue(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeSingleValue(operand) => {
-                ReturnValue::EdgeSingleValue(operand.evaluate_backward(self.medrecord)?)
-            }
-        };
-
-        Ok(result)
-    }
-}
-
-#[derive(Debug, Clone)]
-pub enum ReturnOperand {
-    NodeAttributesTree(Wrapper<NodeAttributesTreeOperand>),
-    EdgeAttributesTree(Wrapper<EdgeAttributesTreeOperand>),
-    NodeMultipleAttributes(Wrapper<NodeMultipleAttributesOperand>),
-    EdgeMultipleAttributes(Wrapper<EdgeMultipleAttributesOperand>),
-    NodeSingleAttribute(Wrapper<NodeSingleAttributeOperand>),
-    EdgeSingleAttribute(Wrapper<EdgeSingleAttributeOperand>),
-    EdgeIndices(Wrapper<EdgeIndicesOperand>),
-    EdgeIndex(Wrapper<EdgeIndexOperand>),
-    NodeIndices(Wrapper<NodeIndicesOperand>),
-    NodeIndex(Wrapper<NodeIndexOperand>),
-    NodeMultipleValues(Wrapper<NodeMultipleValuesOperand>),
-    EdgeMultipleValues(Wrapper<EdgeMultipleValuesOperand>),
-    NodeSingleValue(Wrapper<NodeSingleValueOperand>),
-    EdgeSingleValue(Wrapper<EdgeSingleValueOperand>),
-}
-
-impl From<Wrapper<NodeAttributesTreeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeAttributesTreeOperand>) -> Self {
-        Self::NodeAttributesTree(operand)
-    }
-}
-
-impl From<Wrapper<EdgeAttributesTreeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeAttributesTreeOperand>) -> Self {
-        Self::EdgeAttributesTree(operand)
-    }
-}
-
-impl From<Wrapper<NodeMultipleAttributesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeMultipleAttributesOperand>) -> Self {
-        Self::NodeMultipleAttributes(operand)
-    }
-}
-
-impl From<Wrapper<EdgeMultipleAttributesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeMultipleAttributesOperand>) -> Self {
-        Self::EdgeMultipleAttributes(operand)
-    }
-}
-
-impl From<Wrapper<NodeSingleAttributeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeSingleAttributeOperand>) -> Self {
-        Self::NodeSingleAttribute(operand)
-    }
-}
-
-impl From<Wrapper<EdgeSingleAttributeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeSingleAttributeOperand>) -> Self {
-        Self::EdgeSingleAttribute(operand)
-    }
-}
-
-impl From<Wrapper<EdgeIndicesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeIndicesOperand>) -> Self {
-        Self::EdgeIndices(operand)
-    }
-}
-
-impl From<Wrapper<EdgeIndexOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeIndexOperand>) -> Self {
-        Self::EdgeIndex(operand)
-    }
-}
-
-impl From<Wrapper<NodeIndicesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeIndicesOperand>) -> Self {
-        Self::NodeIndices(operand)
-    }
-}
-
-impl From<Wrapper<NodeIndexOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeIndexOperand>) -> Self {
-        Self::NodeIndex(operand)
-    }
-}
-
-impl From<Wrapper<NodeMultipleValuesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeMultipleValuesOperand>) -> Self {
-        Self::NodeMultipleValues(operand)
-    }
-}
-
-impl From<Wrapper<EdgeMultipleValuesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeMultipleValuesOperand>) -> Self {
-        Self::EdgeMultipleValues(operand)
-    }
-}
-
-impl From<Wrapper<NodeSingleValueOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeSingleValueOperand>) -> Self {
-        Self::NodeSingleValue(operand)
-    }
-}
-
-impl From<Wrapper<EdgeSingleValueOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeSingleValueOperand>) -> Self {
-        Self::EdgeSingleValue(operand)
-    }
-}
-
-pub enum ReturnValue<'a> {
-    NodeAttributesTree(Box<dyn Iterator<Item = (&'a NodeIndex, Vec<MedRecordAttribute>)> + 'a>),
-    EdgeAttributesTree(Box<dyn Iterator<Item = (&'a EdgeIndex, Vec<MedRecordAttribute>)> + 'a>),
-    NodeMultipleAttributes(Box<dyn Iterator<Item = (&'a NodeIndex, MedRecordAttribute)> + 'a>),
-    EdgeMultipleAttributes(Box<dyn Iterator<Item = (&'a EdgeIndex, MedRecordAttribute)> + 'a>),
-    NodeSingleAttribute(Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordAttribute>>),
-    EdgeSingleAttribute(Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordAttribute>>),
-    EdgeIndices(Box<dyn Iterator<Item = EdgeIndex> + 'a>),
-    EdgeIndex(Option<EdgeIndex>),
-    NodeIndices(Box<dyn Iterator<Item = NodeIndex> + 'a>),
-    NodeIndex(Option<NodeIndex>),
-    NodeMultipleValues(Box<dyn Iterator<Item = (&'a NodeIndex, MedRecordValue)> + 'a>),
-    EdgeMultipleValues(Box<dyn Iterator<Item = (&'a EdgeIndex, MedRecordValue)> + 'a>),
-    NodeSingleValue(Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordValue>>),
-    EdgeSingleValue(Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordValue>>),
-}
+    pub fn evaluate(self) -> MedRecordResult<R::ReturnValue> {
+        self.return_operand.evaluate(self.medrecord)
+    }
+}
+
+pub trait ReturnOperand<'a> {
+    type ReturnValue;
+
+    fn evaluate(self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue>;
+}
+
+impl_iterator_return_operand!(
+    NodeAttributesTreeOperand     => (&'a NodeIndex, Vec<MedRecordAttribute>),
+    EdgeAttributesTreeOperand     => (&'a EdgeIndex, Vec<MedRecordAttribute>),
+    NodeMultipleAttributesOperand => (&'a NodeIndex, MedRecordAttribute),
+    EdgeMultipleAttributesOperand => (&'a EdgeIndex, MedRecordAttribute),
+    EdgeIndicesOperand            => EdgeIndex,
+    NodeIndicesOperand            => NodeIndex,
+    NodeMultipleValuesOperand     => (&'a NodeIndex, MedRecordValue),
+    EdgeMultipleValuesOperand     => (&'a EdgeIndex, MedRecordValue),
+);
+
+impl_direct_return_operand!(
+    NodeSingleAttributeOperand => Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordAttribute>>,
+    EdgeSingleAttributeOperand => Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordAttribute>>,
+    EdgeIndexOperand           => Option<EdgeIndex>,
+    NodeIndexOperand           => Option<NodeIndex>,
+    NodeSingleValueOperand     => Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordValue>>,
+    EdgeSingleValueOperand     => Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordValue>>,
+);
+
+impl_return_operand_for_tuples!(R1, R2);
+impl_return_operand_for_tuples!(R1, R2, R3);
+impl_return_operand_for_tuples!(R1, R2, R3, R4);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15);
path: crates/medmodels-core/src/medrecord/querying/mod.rs
user: login: JabobKrauskopf
body: In the rust query engine, you can return tuples (not lists as in the python query engine - that would not be possible/useful). However you can't just say I want any length of tuple to be returnable. You have to implement the return operand trait for each length of tuple (1, 2, 3, 4, 5, etc.). These macro calls do that for tuples up to a length of 15 elements. 
R1, R2, etc. are Generics for each return operand
created_at: 2025-05-14T14:22:00Z
author_association: MEMBER
diff_hunk: @@ -3,24 +3,32 @@ pub mod edges;
 pub mod nodes;
 pub mod values;
 
+use crate::gil_hash_map::GILHashMap;
+
 use super::{
     attribute::PyMedRecordAttribute, errors::PyMedRecordError, traits::DeepFrom,
-    value::PyMedRecordValue, PyNodeIndex,
+    value::PyMedRecordValue, Lut, PyNodeIndex,
path: rustmodels/src/medrecord/querying/mod.rs
user: login: JabobKrauskopf
body: LookUpTable. `Lut` is a term that is established in low-level languages
created_at: 2025-05-14T14:24:07Z
author_association: MEMBER
diff_hunk: @@ -101,203 +146,78 @@ impl<I: Index, T> From<(I, T)> for OptionalIndexWrapper<I, T> {
 }
 
 #[derive(Debug, Clone)]
-pub struct Selection<'a> {
+pub struct Selection<'a, R: ReturnOperand<'a>> {
     medrecord: &'a MedRecord,
-    return_operand: ReturnOperand,
+    return_operand: R,
 }
 
-impl<'a> Selection<'a> {
-    pub fn new_node<Q, R>(medrecord: &'a MedRecord, query: Q) -> Self
+impl<'a, R: ReturnOperand<'a>> Selection<'a, R> {
+    pub fn new_node<Q>(medrecord: &'a MedRecord, query: Q) -> Self
     where
         Q: FnOnce(&mut Wrapper<NodeOperand>) -> R,
-        R: Into<ReturnOperand>,
     {
         let mut operand = Wrapper::<NodeOperand>::new(None);
 
         Self {
             medrecord,
-            return_operand: query(&mut operand).into(),
+            return_operand: query(&mut operand),
         }
     }
 
-    pub fn new_edge<Q, R>(medrecord: &'a MedRecord, query: Q) -> Self
+    pub fn new_edge<Q>(medrecord: &'a MedRecord, query: Q) -> Self
     where
         Q: FnOnce(&mut Wrapper<EdgeOperand>) -> R,
-        R: Into<ReturnOperand>,
     {
         let mut operand = Wrapper::<EdgeOperand>::new(None);
 
         Self {
             medrecord,
-            return_operand: query(&mut operand).into(),
+            return_operand: query(&mut operand),
         }
     }
 
-    pub fn evaluate(self) -> MedRecordResult<ReturnValue<'a>> {
-        let result = match self.return_operand {
-            ReturnOperand::NodeAttributesTree(operand) => ReturnValue::NodeAttributesTree(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeAttributesTree(operand) => ReturnValue::EdgeAttributesTree(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeMultipleAttributes(operand) => ReturnValue::NodeMultipleAttributes(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeMultipleAttributes(operand) => ReturnValue::EdgeMultipleAttributes(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeSingleAttribute(operand) => {
-                ReturnValue::NodeSingleAttribute(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeSingleAttribute(operand) => {
-                ReturnValue::EdgeSingleAttribute(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeIndices(operand) => {
-                ReturnValue::EdgeIndices(Box::new(operand.evaluate_backward(self.medrecord)?))
-            }
-            ReturnOperand::EdgeIndex(operand) => {
-                ReturnValue::EdgeIndex(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::NodeIndices(operand) => {
-                ReturnValue::NodeIndices(Box::new(operand.evaluate_backward(self.medrecord)?))
-            }
-            ReturnOperand::NodeIndex(operand) => {
-                ReturnValue::NodeIndex(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::NodeMultipleValues(operand) => ReturnValue::NodeMultipleValues(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeMultipleValues(operand) => ReturnValue::EdgeMultipleValues(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeSingleValue(operand) => {
-                ReturnValue::NodeSingleValue(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeSingleValue(operand) => {
-                ReturnValue::EdgeSingleValue(operand.evaluate_backward(self.medrecord)?)
-            }
-        };
-
-        Ok(result)
-    }
-}
-
-#[derive(Debug, Clone)]
-pub enum ReturnOperand {
-    NodeAttributesTree(Wrapper<NodeAttributesTreeOperand>),
-    EdgeAttributesTree(Wrapper<EdgeAttributesTreeOperand>),
-    NodeMultipleAttributes(Wrapper<NodeMultipleAttributesOperand>),
-    EdgeMultipleAttributes(Wrapper<EdgeMultipleAttributesOperand>),
-    NodeSingleAttribute(Wrapper<NodeSingleAttributeOperand>),
-    EdgeSingleAttribute(Wrapper<EdgeSingleAttributeOperand>),
-    EdgeIndices(Wrapper<EdgeIndicesOperand>),
-    EdgeIndex(Wrapper<EdgeIndexOperand>),
-    NodeIndices(Wrapper<NodeIndicesOperand>),
-    NodeIndex(Wrapper<NodeIndexOperand>),
-    NodeMultipleValues(Wrapper<NodeMultipleValuesOperand>),
-    EdgeMultipleValues(Wrapper<EdgeMultipleValuesOperand>),
-    NodeSingleValue(Wrapper<NodeSingleValueOperand>),
-    EdgeSingleValue(Wrapper<EdgeSingleValueOperand>),
-}
-
-impl From<Wrapper<NodeAttributesTreeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeAttributesTreeOperand>) -> Self {
-        Self::NodeAttributesTree(operand)
-    }
-}
-
-impl From<Wrapper<EdgeAttributesTreeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeAttributesTreeOperand>) -> Self {
-        Self::EdgeAttributesTree(operand)
-    }
-}
-
-impl From<Wrapper<NodeMultipleAttributesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeMultipleAttributesOperand>) -> Self {
-        Self::NodeMultipleAttributes(operand)
-    }
-}
-
-impl From<Wrapper<EdgeMultipleAttributesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeMultipleAttributesOperand>) -> Self {
-        Self::EdgeMultipleAttributes(operand)
-    }
-}
-
-impl From<Wrapper<NodeSingleAttributeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeSingleAttributeOperand>) -> Self {
-        Self::NodeSingleAttribute(operand)
-    }
-}
-
-impl From<Wrapper<EdgeSingleAttributeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeSingleAttributeOperand>) -> Self {
-        Self::EdgeSingleAttribute(operand)
-    }
-}
-
-impl From<Wrapper<EdgeIndicesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeIndicesOperand>) -> Self {
-        Self::EdgeIndices(operand)
-    }
-}
-
-impl From<Wrapper<EdgeIndexOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeIndexOperand>) -> Self {
-        Self::EdgeIndex(operand)
-    }
-}
-
-impl From<Wrapper<NodeIndicesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeIndicesOperand>) -> Self {
-        Self::NodeIndices(operand)
-    }
-}
-
-impl From<Wrapper<NodeIndexOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeIndexOperand>) -> Self {
-        Self::NodeIndex(operand)
-    }
-}
-
-impl From<Wrapper<NodeMultipleValuesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeMultipleValuesOperand>) -> Self {
-        Self::NodeMultipleValues(operand)
-    }
-}
-
-impl From<Wrapper<EdgeMultipleValuesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeMultipleValuesOperand>) -> Self {
-        Self::EdgeMultipleValues(operand)
-    }
-}
-
-impl From<Wrapper<NodeSingleValueOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeSingleValueOperand>) -> Self {
-        Self::NodeSingleValue(operand)
-    }
-}
-
-impl From<Wrapper<EdgeSingleValueOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeSingleValueOperand>) -> Self {
-        Self::EdgeSingleValue(operand)
-    }
-}
-
-pub enum ReturnValue<'a> {
-    NodeAttributesTree(Box<dyn Iterator<Item = (&'a NodeIndex, Vec<MedRecordAttribute>)> + 'a>),
-    EdgeAttributesTree(Box<dyn Iterator<Item = (&'a EdgeIndex, Vec<MedRecordAttribute>)> + 'a>),
-    NodeMultipleAttributes(Box<dyn Iterator<Item = (&'a NodeIndex, MedRecordAttribute)> + 'a>),
-    EdgeMultipleAttributes(Box<dyn Iterator<Item = (&'a EdgeIndex, MedRecordAttribute)> + 'a>),
-    NodeSingleAttribute(Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordAttribute>>),
-    EdgeSingleAttribute(Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordAttribute>>),
-    EdgeIndices(Box<dyn Iterator<Item = EdgeIndex> + 'a>),
-    EdgeIndex(Option<EdgeIndex>),
-    NodeIndices(Box<dyn Iterator<Item = NodeIndex> + 'a>),
-    NodeIndex(Option<NodeIndex>),
-    NodeMultipleValues(Box<dyn Iterator<Item = (&'a NodeIndex, MedRecordValue)> + 'a>),
-    EdgeMultipleValues(Box<dyn Iterator<Item = (&'a EdgeIndex, MedRecordValue)> + 'a>),
-    NodeSingleValue(Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordValue>>),
-    EdgeSingleValue(Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordValue>>),
-}
+    pub fn evaluate(self) -> MedRecordResult<R::ReturnValue> {
+        self.return_operand.evaluate(self.medrecord)
+    }
+}
+
+pub trait ReturnOperand<'a> {
+    type ReturnValue;
+
+    fn evaluate(self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue>;
+}
+
+impl_iterator_return_operand!(
+    NodeAttributesTreeOperand     => (&'a NodeIndex, Vec<MedRecordAttribute>),
+    EdgeAttributesTreeOperand     => (&'a EdgeIndex, Vec<MedRecordAttribute>),
+    NodeMultipleAttributesOperand => (&'a NodeIndex, MedRecordAttribute),
+    EdgeMultipleAttributesOperand => (&'a EdgeIndex, MedRecordAttribute),
+    EdgeIndicesOperand            => EdgeIndex,
+    NodeIndicesOperand            => NodeIndex,
+    NodeMultipleValuesOperand     => (&'a NodeIndex, MedRecordValue),
+    EdgeMultipleValuesOperand     => (&'a EdgeIndex, MedRecordValue),
+);
+
+impl_direct_return_operand!(
+    NodeSingleAttributeOperand => Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordAttribute>>,
+    EdgeSingleAttributeOperand => Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordAttribute>>,
+    EdgeIndexOperand           => Option<EdgeIndex>,
+    NodeIndexOperand           => Option<NodeIndex>,
+    NodeSingleValueOperand     => Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordValue>>,
+    EdgeSingleValueOperand     => Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordValue>>,
+);
+
+impl_return_operand_for_tuples!(R1, R2);
+impl_return_operand_for_tuples!(R1, R2, R3);
+impl_return_operand_for_tuples!(R1, R2, R3, R4);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15);
path: crates/medmodels-core/src/medrecord/querying/mod.rs
user: login: MarIniOnz
body: okay, so we allow up to 15 returns
created_at: 2025-05-14T14:25:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -101,203 +146,78 @@ impl<I: Index, T> From<(I, T)> for OptionalIndexWrapper<I, T> {
 }
 
 #[derive(Debug, Clone)]
-pub struct Selection<'a> {
+pub struct Selection<'a, R: ReturnOperand<'a>> {
     medrecord: &'a MedRecord,
-    return_operand: ReturnOperand,
+    return_operand: R,
 }
 
-impl<'a> Selection<'a> {
-    pub fn new_node<Q, R>(medrecord: &'a MedRecord, query: Q) -> Self
+impl<'a, R: ReturnOperand<'a>> Selection<'a, R> {
+    pub fn new_node<Q>(medrecord: &'a MedRecord, query: Q) -> Self
     where
         Q: FnOnce(&mut Wrapper<NodeOperand>) -> R,
-        R: Into<ReturnOperand>,
     {
         let mut operand = Wrapper::<NodeOperand>::new(None);
 
         Self {
             medrecord,
-            return_operand: query(&mut operand).into(),
+            return_operand: query(&mut operand),
         }
     }
 
-    pub fn new_edge<Q, R>(medrecord: &'a MedRecord, query: Q) -> Self
+    pub fn new_edge<Q>(medrecord: &'a MedRecord, query: Q) -> Self
     where
         Q: FnOnce(&mut Wrapper<EdgeOperand>) -> R,
-        R: Into<ReturnOperand>,
     {
         let mut operand = Wrapper::<EdgeOperand>::new(None);
 
         Self {
             medrecord,
-            return_operand: query(&mut operand).into(),
+            return_operand: query(&mut operand),
         }
     }
 
-    pub fn evaluate(self) -> MedRecordResult<ReturnValue<'a>> {
-        let result = match self.return_operand {
-            ReturnOperand::NodeAttributesTree(operand) => ReturnValue::NodeAttributesTree(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeAttributesTree(operand) => ReturnValue::EdgeAttributesTree(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeMultipleAttributes(operand) => ReturnValue::NodeMultipleAttributes(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeMultipleAttributes(operand) => ReturnValue::EdgeMultipleAttributes(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeSingleAttribute(operand) => {
-                ReturnValue::NodeSingleAttribute(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeSingleAttribute(operand) => {
-                ReturnValue::EdgeSingleAttribute(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeIndices(operand) => {
-                ReturnValue::EdgeIndices(Box::new(operand.evaluate_backward(self.medrecord)?))
-            }
-            ReturnOperand::EdgeIndex(operand) => {
-                ReturnValue::EdgeIndex(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::NodeIndices(operand) => {
-                ReturnValue::NodeIndices(Box::new(operand.evaluate_backward(self.medrecord)?))
-            }
-            ReturnOperand::NodeIndex(operand) => {
-                ReturnValue::NodeIndex(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::NodeMultipleValues(operand) => ReturnValue::NodeMultipleValues(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::EdgeMultipleValues(operand) => ReturnValue::EdgeMultipleValues(
-                Box::new(operand.evaluate_backward(self.medrecord)?),
-            ),
-            ReturnOperand::NodeSingleValue(operand) => {
-                ReturnValue::NodeSingleValue(operand.evaluate_backward(self.medrecord)?)
-            }
-            ReturnOperand::EdgeSingleValue(operand) => {
-                ReturnValue::EdgeSingleValue(operand.evaluate_backward(self.medrecord)?)
-            }
-        };
-
-        Ok(result)
-    }
-}
-
-#[derive(Debug, Clone)]
-pub enum ReturnOperand {
-    NodeAttributesTree(Wrapper<NodeAttributesTreeOperand>),
-    EdgeAttributesTree(Wrapper<EdgeAttributesTreeOperand>),
-    NodeMultipleAttributes(Wrapper<NodeMultipleAttributesOperand>),
-    EdgeMultipleAttributes(Wrapper<EdgeMultipleAttributesOperand>),
-    NodeSingleAttribute(Wrapper<NodeSingleAttributeOperand>),
-    EdgeSingleAttribute(Wrapper<EdgeSingleAttributeOperand>),
-    EdgeIndices(Wrapper<EdgeIndicesOperand>),
-    EdgeIndex(Wrapper<EdgeIndexOperand>),
-    NodeIndices(Wrapper<NodeIndicesOperand>),
-    NodeIndex(Wrapper<NodeIndexOperand>),
-    NodeMultipleValues(Wrapper<NodeMultipleValuesOperand>),
-    EdgeMultipleValues(Wrapper<EdgeMultipleValuesOperand>),
-    NodeSingleValue(Wrapper<NodeSingleValueOperand>),
-    EdgeSingleValue(Wrapper<EdgeSingleValueOperand>),
-}
-
-impl From<Wrapper<NodeAttributesTreeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeAttributesTreeOperand>) -> Self {
-        Self::NodeAttributesTree(operand)
-    }
-}
-
-impl From<Wrapper<EdgeAttributesTreeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeAttributesTreeOperand>) -> Self {
-        Self::EdgeAttributesTree(operand)
-    }
-}
-
-impl From<Wrapper<NodeMultipleAttributesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeMultipleAttributesOperand>) -> Self {
-        Self::NodeMultipleAttributes(operand)
-    }
-}
-
-impl From<Wrapper<EdgeMultipleAttributesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeMultipleAttributesOperand>) -> Self {
-        Self::EdgeMultipleAttributes(operand)
-    }
-}
-
-impl From<Wrapper<NodeSingleAttributeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeSingleAttributeOperand>) -> Self {
-        Self::NodeSingleAttribute(operand)
-    }
-}
-
-impl From<Wrapper<EdgeSingleAttributeOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeSingleAttributeOperand>) -> Self {
-        Self::EdgeSingleAttribute(operand)
-    }
-}
-
-impl From<Wrapper<EdgeIndicesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeIndicesOperand>) -> Self {
-        Self::EdgeIndices(operand)
-    }
-}
-
-impl From<Wrapper<EdgeIndexOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeIndexOperand>) -> Self {
-        Self::EdgeIndex(operand)
-    }
-}
-
-impl From<Wrapper<NodeIndicesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeIndicesOperand>) -> Self {
-        Self::NodeIndices(operand)
-    }
-}
-
-impl From<Wrapper<NodeIndexOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeIndexOperand>) -> Self {
-        Self::NodeIndex(operand)
-    }
-}
-
-impl From<Wrapper<NodeMultipleValuesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeMultipleValuesOperand>) -> Self {
-        Self::NodeMultipleValues(operand)
-    }
-}
-
-impl From<Wrapper<EdgeMultipleValuesOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeMultipleValuesOperand>) -> Self {
-        Self::EdgeMultipleValues(operand)
-    }
-}
-
-impl From<Wrapper<NodeSingleValueOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<NodeSingleValueOperand>) -> Self {
-        Self::NodeSingleValue(operand)
-    }
-}
-
-impl From<Wrapper<EdgeSingleValueOperand>> for ReturnOperand {
-    fn from(operand: Wrapper<EdgeSingleValueOperand>) -> Self {
-        Self::EdgeSingleValue(operand)
-    }
-}
-
-pub enum ReturnValue<'a> {
-    NodeAttributesTree(Box<dyn Iterator<Item = (&'a NodeIndex, Vec<MedRecordAttribute>)> + 'a>),
-    EdgeAttributesTree(Box<dyn Iterator<Item = (&'a EdgeIndex, Vec<MedRecordAttribute>)> + 'a>),
-    NodeMultipleAttributes(Box<dyn Iterator<Item = (&'a NodeIndex, MedRecordAttribute)> + 'a>),
-    EdgeMultipleAttributes(Box<dyn Iterator<Item = (&'a EdgeIndex, MedRecordAttribute)> + 'a>),
-    NodeSingleAttribute(Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordAttribute>>),
-    EdgeSingleAttribute(Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordAttribute>>),
-    EdgeIndices(Box<dyn Iterator<Item = EdgeIndex> + 'a>),
-    EdgeIndex(Option<EdgeIndex>),
-    NodeIndices(Box<dyn Iterator<Item = NodeIndex> + 'a>),
-    NodeIndex(Option<NodeIndex>),
-    NodeMultipleValues(Box<dyn Iterator<Item = (&'a NodeIndex, MedRecordValue)> + 'a>),
-    EdgeMultipleValues(Box<dyn Iterator<Item = (&'a EdgeIndex, MedRecordValue)> + 'a>),
-    NodeSingleValue(Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordValue>>),
-    EdgeSingleValue(Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordValue>>),
-}
+    pub fn evaluate(self) -> MedRecordResult<R::ReturnValue> {
+        self.return_operand.evaluate(self.medrecord)
+    }
+}
+
+pub trait ReturnOperand<'a> {
+    type ReturnValue;
+
+    fn evaluate(self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue>;
+}
+
+impl_iterator_return_operand!(
+    NodeAttributesTreeOperand     => (&'a NodeIndex, Vec<MedRecordAttribute>),
+    EdgeAttributesTreeOperand     => (&'a EdgeIndex, Vec<MedRecordAttribute>),
+    NodeMultipleAttributesOperand => (&'a NodeIndex, MedRecordAttribute),
+    EdgeMultipleAttributesOperand => (&'a EdgeIndex, MedRecordAttribute),
+    EdgeIndicesOperand            => EdgeIndex,
+    NodeIndicesOperand            => NodeIndex,
+    NodeMultipleValuesOperand     => (&'a NodeIndex, MedRecordValue),
+    EdgeMultipleValuesOperand     => (&'a EdgeIndex, MedRecordValue),
+);
+
+impl_direct_return_operand!(
+    NodeSingleAttributeOperand => Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordAttribute>>,
+    EdgeSingleAttributeOperand => Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordAttribute>>,
+    EdgeIndexOperand           => Option<EdgeIndex>,
+    NodeIndexOperand           => Option<NodeIndex>,
+    NodeSingleValueOperand     => Option<OptionalIndexWrapper<&'a NodeIndex, MedRecordValue>>,
+    EdgeSingleValueOperand     => Option<OptionalIndexWrapper<&'a EdgeIndex, MedRecordValue>>,
+);
+
+impl_return_operand_for_tuples!(R1, R2);
+impl_return_operand_for_tuples!(R1, R2, R3);
+impl_return_operand_for_tuples!(R1, R2, R3, R4);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14);
+impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15);
path: crates/medmodels-core/src/medrecord/querying/mod.rs
user: login: JabobKrauskopf
body: In rust yes. On one "level". Tuples can also be nested so technically any number of return operands is possible, it's just not as straight forward
created_at: 2025-05-14T14:27:50Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-13T09:35:24Z
body: 
  Thank you for your pull request! 🎉

  Please make sure to read the ***pull request guidelines*** at: https://www.medmodels.de/docs/latest/developer_guide/pull-request.html

  While you're waiting for a review, please ensure that:

  * All required checks have passed.
  * Documentation is updated if necessary.

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-13T11:47:22Z
body: # ☂️ Python Coverage
> current status: ✅
## Overall Coverage
| Lines | Covered | Coverage | Threshold | Status |
| :---: | :-----: | :------: | :-------: | :----: |
|  3536 |   3536  |   100%   |    100%   |   🟢   |
## New Files
No new covered files...
## Modified Files
| File                             | Coverage | Status |
| :------------------------------- | :------: | :----: |
| medmodels/medrecord/medrecord.py |   100%   |   🟢   |
| medmodels/medrecord/querying.py  |   100%   |   🟢   |
| medmodels/medrecord/types.py     |   100%   |   🟢   |
| **TOTAL**                        | **100%** |   🟢   |


> **updated for commit: `7920c2e` by [action](https://github.com/marketplace/actions/python-coverage)🐍**
author_association: NONE