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