Skip to main content

elasticctl_api/search/
mod.rs

1pub mod dataview;
2pub mod dsl;
3pub mod esql;
4
5use elasticctl_core::{Result, Transport};
6
7pub struct SearchRequest {
8    pub index: Option<String>,
9    pub data_view: Option<String>,
10    pub limit: Option<usize>,
11}
12
13fn skip_leading_comments(query: &str) -> &str {
14    let mut rest = query;
15    loop {
16        rest = rest.trim_start();
17        if rest.starts_with("//") {
18            rest = &rest[rest.find('\n').map_or(rest.len(), |end| end + 1)..];
19        } else if rest.starts_with("/*") {
20            let Some(end) = rest.find("*/") else {
21                return "";
22            };
23            rest = &rest[end + 2..];
24        } else {
25            return rest;
26        }
27    }
28}
29
30pub fn has_source(query: &str) -> bool {
31    let rest = skip_leading_comments(query);
32    let word = &rest[..rest.find(char::is_whitespace).unwrap_or(rest.len())];
33    matches!(
34        word.to_ascii_lowercase().as_str(),
35        "from" | "row" | "show" | "metrics"
36    )
37}
38
39pub fn prepend_from(query: &str, pattern: &str) -> String {
40    if has_source(query) {
41        query.to_string()
42    } else {
43        let q = query.trim_start();
44        let sep = if q.starts_with('|') { "" } else { "| " };
45        format!("FROM {pattern} {sep}{q}")
46    }
47}
48
49pub fn rewrite_from(query: &str, pattern: &str) -> String {
50    let rest = skip_leading_comments(query);
51    let leading = &query[..query.len() - rest.len()];
52    let word_end = rest.find(char::is_whitespace).unwrap_or(rest.len());
53    let word = &rest[..word_end];
54    if word.eq_ignore_ascii_case("from") {
55        let after = &rest[word_end..];
56        let source_end = after.find('|').unwrap_or(after.len());
57        let pipeline = &after[source_end..];
58        if pipeline.is_empty() {
59            format!("{leading}FROM {pattern}")
60        } else {
61            format!("{leading}FROM {pattern} {pipeline}")
62        }
63    } else if word.eq_ignore_ascii_case("row")
64        || word.eq_ignore_ascii_case("show")
65        || word.eq_ignore_ascii_case("metrics")
66    {
67        query.to_string()
68    } else {
69        prepend_from(query, pattern)
70    }
71}
72
73pub async fn resolve_esql_query(t: &Transport, query: &str, req: &SearchRequest) -> Result<String> {
74    match (&req.index, &req.data_view) {
75        (Some(i), _) => Ok(rewrite_from(query, i)),
76        (_, Some(dv)) => Ok(rewrite_from(query, &dataview::resolve(t, dv).await?)),
77        _ => {
78            if has_source(query) {
79                Ok(query.to_string())
80            } else {
81                Ok(prepend_from(
82                    query,
83                    &dataview::default_alerts_index(t).await?,
84                ))
85            }
86        }
87    }
88}
89
90pub async fn resolve_dsl_index(t: &Transport, req: &SearchRequest) -> Result<String> {
91    match (&req.index, &req.data_view) {
92        (Some(i), _) => Ok(i.clone()),
93        (_, Some(dv)) => dataview::resolve(t, dv).await,
94        _ => dataview::default_alerts_index(t).await,
95    }
96}