use crate::graph::node::Language;
use crate::graph::unified::concurrent::CodeGraph;
use crate::graph::unified::node::{NodeId, NodeKind};
use crate::graph::unified::storage::arena::NodeEntry;
use crate::query::pipeline::AggregationResult;
use crate::query::types::JoinEdgeKind;
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub struct QueryResults {
graph: Arc<CodeGraph>,
matches: Vec<NodeId>,
workspace_root: Option<PathBuf>,
}
impl QueryResults {
#[must_use]
pub fn new(graph: Arc<CodeGraph>, matches: Vec<NodeId>) -> Self {
Self {
graph,
matches,
workspace_root: None,
}
}
#[must_use]
pub fn with_workspace_root(mut self, root: PathBuf) -> Self {
self.workspace_root = Some(root);
self
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.matches.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.matches.is_empty()
}
#[must_use]
pub fn node_ids(&self) -> &[NodeId] {
&self.matches
}
#[must_use]
pub fn graph(&self) -> &CodeGraph {
&self.graph
}
#[must_use]
pub fn workspace_root(&self) -> Option<&Path> {
self.workspace_root.as_deref()
}
pub fn iter(&self) -> impl Iterator<Item = QueryMatch<'_>> + '_ {
self.matches.iter().filter_map(|&id| {
self.graph.nodes().get(id).map(|entry| QueryMatch {
id,
entry,
graph: &self.graph,
workspace_root: self.workspace_root.as_deref(),
})
})
}
pub fn sort_by_location(&mut self) {
let graph = &self.graph;
self.matches.sort_by(|&a, &b| {
let ea = graph.nodes().get(a);
let eb = graph.nodes().get(b);
match (ea, eb) {
(Some(ea), Some(eb)) => {
let path_a = graph.files().resolve(ea.file);
let path_b = graph.files().resolve(eb.file);
let name_a = graph.strings().resolve(ea.name);
let name_b = graph.strings().resolve(eb.name);
path_a
.cmp(&path_b)
.then(ea.start_line.cmp(&eb.start_line))
.then(name_a.cmp(&name_b))
}
_ => std::cmp::Ordering::Equal,
}
});
}
#[must_use]
pub fn into_node_ids(self) -> Vec<NodeId> {
self.matches
}
#[must_use]
pub fn into_parts(self) -> (Arc<CodeGraph>, Vec<NodeId>) {
(self.graph, self.matches)
}
}
impl std::fmt::Debug for QueryResults {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QueryResults")
.field("match_count", &self.matches.len())
.field("graph", &self.graph)
.field("workspace_root", &self.workspace_root)
.finish()
}
}
pub struct QueryMatch<'a> {
pub id: NodeId,
pub entry: &'a NodeEntry,
graph: &'a CodeGraph,
workspace_root: Option<&'a Path>,
}
impl QueryMatch<'_> {
#[must_use]
pub fn name(&self) -> Option<Arc<str>> {
self.graph.strings().resolve(self.entry.name)
}
#[must_use]
pub fn kind(&self) -> NodeKind {
self.entry.kind
}
#[must_use]
pub fn file_path(&self) -> Option<Arc<Path>> {
self.graph.files().resolve(self.entry.file)
}
#[must_use]
pub fn relative_path(&self) -> Option<PathBuf> {
let path = self.file_path()?;
if let Some(root) = self.workspace_root {
path.strip_prefix(root)
.ok()
.map(std::path::Path::to_path_buf)
} else {
Some(path.to_path_buf())
}
}
#[inline]
#[must_use]
pub fn start_line(&self) -> u32 {
self.entry.start_line
}
#[inline]
#[must_use]
pub fn end_line(&self) -> u32 {
self.entry.end_line
}
#[inline]
#[must_use]
pub fn start_column(&self) -> u32 {
self.entry.start_column
}
#[inline]
#[must_use]
pub fn end_column(&self) -> u32 {
self.entry.end_column
}
#[inline]
#[must_use]
pub fn start_byte(&self) -> u32 {
self.entry.start_byte
}
#[inline]
#[must_use]
pub fn end_byte(&self) -> u32 {
self.entry.end_byte
}
#[must_use]
pub fn visibility(&self) -> Option<Arc<str>> {
self.entry
.visibility
.and_then(|id| self.graph.strings().resolve(id))
}
#[must_use]
pub fn signature(&self) -> Option<Arc<str>> {
self.entry
.signature
.and_then(|id| self.graph.strings().resolve(id))
}
#[must_use]
pub fn qualified_name(&self) -> Option<Arc<str>> {
self.entry
.qualified_name
.and_then(|id| self.graph.strings().resolve(id))
}
#[must_use]
pub fn doc(&self) -> Option<Arc<str>> {
self.entry
.doc
.and_then(|id| self.graph.strings().resolve(id))
}
#[inline]
#[must_use]
pub fn is_async(&self) -> bool {
self.entry.is_async
}
#[inline]
#[must_use]
pub fn is_static(&self) -> bool {
self.entry.is_static
}
#[must_use]
pub fn language(&self) -> Option<Language> {
self.graph.files().language_for_file(self.entry.file)
}
#[must_use]
pub fn graph(&self) -> &CodeGraph {
self.graph
}
}
impl std::fmt::Debug for QueryMatch<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QueryMatch")
.field("id", &self.id)
.field("kind", &self.entry.kind)
.field("name", &self.name())
.field("start_line", &self.entry.start_line)
.finish()
}
}
pub struct JoinResults {
graph: Arc<CodeGraph>,
pairs: Vec<(NodeId, NodeId)>,
edge_kind: JoinEdgeKind,
workspace_root: Option<PathBuf>,
truncated: bool,
}
impl JoinResults {
#[must_use]
pub fn new(
graph: Arc<CodeGraph>,
pairs: Vec<(NodeId, NodeId)>,
edge_kind: JoinEdgeKind,
truncated: bool,
) -> Self {
Self {
graph,
pairs,
edge_kind,
workspace_root: None,
truncated,
}
}
#[must_use]
pub fn with_workspace_root(mut self, root: PathBuf) -> Self {
self.workspace_root = Some(root);
self
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.pairs.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.pairs.is_empty()
}
#[must_use]
pub fn edge_kind(&self) -> &JoinEdgeKind {
&self.edge_kind
}
#[must_use]
pub fn truncated(&self) -> bool {
self.truncated
}
#[must_use]
pub fn graph(&self) -> &CodeGraph {
&self.graph
}
pub fn iter(&self) -> impl Iterator<Item = JoinMatch<'_>> + '_ {
self.pairs.iter().filter_map(|&(left_id, right_id)| {
let left = self.graph.nodes().get(left_id)?;
let right = self.graph.nodes().get(right_id)?;
Some(JoinMatch {
left: QueryMatch {
id: left_id,
entry: left,
graph: &self.graph,
workspace_root: self.workspace_root.as_deref(),
},
right: QueryMatch {
id: right_id,
entry: right,
graph: &self.graph,
workspace_root: self.workspace_root.as_deref(),
},
edge_kind: &self.edge_kind,
})
})
}
}
impl std::fmt::Debug for JoinResults {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JoinResults")
.field("graph", &self.graph)
.field("pairs", &self.pairs)
.field("edge_kind", &self.edge_kind)
.field("workspace_root", &self.workspace_root)
.field("truncated", &self.truncated)
.finish()
}
}
pub struct JoinMatch<'a> {
pub left: QueryMatch<'a>,
pub right: QueryMatch<'a>,
pub edge_kind: &'a JoinEdgeKind,
}
pub enum QueryOutput {
Results(QueryResults),
Join(JoinResults),
Aggregation(AggregationResult),
}
impl QueryOutput {
#[must_use]
pub fn len(&self) -> usize {
match self {
Self::Results(r) => r.len(),
Self::Join(j) => j.len(),
Self::Aggregation(_) => 0,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
match self {
Self::Results(r) => r.is_empty(),
Self::Join(j) => j.is_empty(),
Self::Aggregation(_) => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_query_results_empty() {
let graph = Arc::new(CodeGraph::new());
let results = QueryResults::new(graph, vec![]);
assert!(results.is_empty());
assert_eq!(results.len(), 0);
assert_eq!(results.iter().count(), 0);
}
#[test]
fn test_query_results_debug() {
let graph = Arc::new(CodeGraph::new());
let results = QueryResults::new(graph, vec![]);
let debug_str = format!("{:?}", results);
assert!(debug_str.contains("QueryResults"));
assert!(debug_str.contains("match_count"));
}
#[test]
fn test_query_results_with_workspace_root() {
let graph = Arc::new(CodeGraph::new());
let results =
QueryResults::new(graph, vec![]).with_workspace_root(PathBuf::from("/test/path"));
assert_eq!(results.workspace_root(), Some(Path::new("/test/path")));
}
#[test]
fn test_query_results_into_parts() {
let graph = Arc::new(CodeGraph::new());
let results = QueryResults::new(graph.clone(), vec![]);
let (returned_graph, matches) = results.into_parts();
assert!(Arc::ptr_eq(&returned_graph, &graph));
assert!(matches.is_empty());
}
}