elasticctl_api/search/
esql.rs1use elasticctl_core::{Error, ErrorKind, Result, Transport};
4use serde_json::Value;
5
6#[derive(Debug, Clone, PartialEq)]
7pub struct EsqlColumn {
8 pub name: String,
9 pub r#type: String,
10}
11
12#[derive(Debug, Clone, PartialEq)]
13pub struct EsqlResponse {
14 pub columns: Vec<EsqlColumn>,
15 pub values: Vec<Vec<Value>>,
16 pub is_partial: bool,
17}
18
19pub fn decode(value: &Value) -> Result<EsqlResponse> {
22 let obj = value.as_object().ok_or_else(|| {
23 Error::new(
24 ErrorKind::Http,
25 "decoding esql response: expected an object",
26 )
27 })?;
28
29 let columns = obj
30 .get("columns")
31 .and_then(Value::as_array)
32 .ok_or_else(|| Error::new(ErrorKind::Http, "decoding esql response field `columns`"))?;
33 let mut out = Vec::with_capacity(columns.len());
34 for (i, col) in columns.iter().enumerate() {
35 let name = col.get("name").and_then(Value::as_str).ok_or_else(|| {
36 Error::new(
37 ErrorKind::Http,
38 format!("decoding esql response column {i} field `name`"),
39 )
40 })?;
41 let ty = col.get("type").and_then(Value::as_str).ok_or_else(|| {
42 Error::new(
43 ErrorKind::Http,
44 format!("decoding esql response column {i} field `type`"),
45 )
46 })?;
47 out.push(EsqlColumn {
48 name: name.to_string(),
49 r#type: ty.to_string(),
50 });
51 }
52
53 let values = obj
54 .get("values")
55 .and_then(Value::as_array)
56 .ok_or_else(|| Error::new(ErrorKind::Http, "decoding esql response field `values`"))?
57 .iter()
58 .map(|row| {
59 row.as_array().cloned().ok_or_else(|| {
60 Error::new(
61 ErrorKind::Http,
62 "decoding esql response: `values` rows must be arrays",
63 )
64 })
65 })
66 .collect::<Result<Vec<_>>>()?;
67
68 let is_partial = obj
69 .get("is_partial")
70 .and_then(Value::as_bool)
71 .unwrap_or(false);
72
73 Ok(EsqlResponse {
74 columns: out,
75 values,
76 is_partial,
77 })
78}
79
80pub async fn run_sync(t: &Transport, query: &str) -> Result<EsqlResponse> {
82 let body = serde_json::json!({ "query": query });
83 let response = t.post_absolute_es("/_query", &body).await?;
84 decode(&response)
85}
86
87pub async fn run_async(t: &Transport, query: &str) -> Result<EsqlResponse> {
90 let start = t
91 .post_absolute_es(
92 "/_query/async",
93 &serde_json::json!({ "query": query, "wait_for_completion_timeout": "1ms", "columnar": false }),
94 )
95 .await?;
96 let id = match start.get("id").and_then(Value::as_str) {
99 Some(id) => id.to_string(),
100 None => return decode(&start),
101 };
102 if start.get("is_running").and_then(Value::as_bool) == Some(false) {
103 return decode(&start);
104 }
105
106 for _ in 0..30 {
109 let resp = t
110 .get_absolute_es(&format!(
111 "/_query/async/{id}?wait_for_completion_timeout=10s"
112 ))
113 .await?;
114 if resp.get("is_running").and_then(Value::as_bool) == Some(false) {
115 let _ = t.delete_absolute_es(&format!("/_query/async/{id}")).await;
116 return decode(&resp);
117 }
118 }
119 let _ = t.delete_absolute_es(&format!("/_query/async/{id}")).await;
120 Err(Error::new(
121 ErrorKind::Timeout,
122 format!("async query {id} still running after 30 polls"),
123 ))
124}