posthog_cli/experimental/query/
mod.rs1use std::collections::HashMap;
2
3use anyhow::Error;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::invocation_context::context;
8
9pub mod command;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct QueryRequest {
15 pub query: Query,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub refresh: Option<QueryRefresh>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(tag = "kind")]
22pub enum Query {
23 HogQLQuery { query: String },
24 HogQLMetadata(MetadataQuery),
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29pub enum QueryRefresh {
30 Blocking,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct MetadataQuery {
35 pub language: MetadataLanguage,
36 pub query: String,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub source: Option<Box<Query>>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub enum MetadataLanguage {
43 #[serde(rename = "hogQL")]
44 HogQL,
45}
46
47pub type HogQLQueryResult = Result<HogQLQueryResponse, HogQLQueryErrorResponse>;
48
49#[derive(Debug, Clone, Deserialize, Serialize)]
50pub struct HogQLQueryResponse {
51 pub cache_key: Option<String>,
52 pub cache_target_age: Option<String>,
53 pub clickhouse: Option<String>, #[serde(default, deserialize_with = "null_is_empty")]
55 pub columns: Vec<String>, pub error: Option<String>,
57 #[serde(default, deserialize_with = "null_is_empty")]
58 pub explain: Vec<String>,
59 #[serde(default, rename = "hasMore", deserialize_with = "null_is_false")]
60 pub has_more: bool,
61 pub hogql: Option<String>, #[serde(default, deserialize_with = "null_is_false")]
63 pub is_cached: bool,
64 pub last_refresh: Option<String>, pub next_allowed_client_refresh_time: Option<String>, pub offset: Option<i64>, pub limit: Option<i64>, pub query: Option<String>, #[serde(default, deserialize_with = "null_is_empty")]
70 pub types: Vec<(String, String)>,
71 #[serde(default, deserialize_with = "null_is_empty")]
72 pub results: Vec<Vec<Value>>,
73 #[serde(default, deserialize_with = "null_is_empty")]
74 pub timings: Vec<Timing>,
75 #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
76 pub other: HashMap<String, Value>,
77}
78
79#[derive(Debug, Clone, Deserialize, Serialize)]
80pub struct HogQLQueryErrorResponse {
81 pub code: String,
82 pub detail: String,
83 #[serde(rename = "type")]
84 pub error_type: String,
85 #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
86 pub other: HashMap<String, Value>,
87}
88
89#[derive(Debug, Clone, Deserialize, Serialize)]
90pub struct Timing {
91 pub k: String,
92 pub t: f64,
93}
94
95#[derive(Debug, Clone, Deserialize, Serialize)]
96pub struct MetadataResponse {
97 #[serde(default, deserialize_with = "null_is_empty")]
98 pub errors: Vec<Notice>,
99 #[serde(default, deserialize_with = "null_is_empty")]
100 pub notices: Vec<Notice>,
101 #[serde(default, deserialize_with = "null_is_empty")]
102 pub warnings: Vec<Notice>,
103 #[serde(default, rename = "isUsingIndices")]
104 pub is_using_indices: Option<IndicesUsage>,
105 #[serde(default, deserialize_with = "null_is_false", rename = "isValid")]
106 pub is_valid: bool,
107 #[serde(default, deserialize_with = "null_is_false")]
108 pub is_valid_view: bool,
109 #[serde(default, deserialize_with = "null_is_empty")]
110 pub table_names: Vec<String>,
111 #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
112 pub other: HashMap<String, Value>,
113}
114
115#[derive(Debug, Clone, Deserialize, Serialize)]
116#[serde(rename_all = "lowercase")]
117pub enum IndicesUsage {
118 Undecisive,
119 No,
120 Partial,
121 Yes,
122}
123
124#[derive(Debug, Clone, Deserialize, Serialize)]
125pub struct Notice {
126 pub message: String,
127 #[serde(flatten)]
128 pub span: Option<NoticeSpan>,
129 #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
130 pub other: HashMap<String, Value>,
131}
132
133#[derive(Debug, Clone, Deserialize, Serialize)]
134pub struct NoticeSpan {
135 pub start: usize,
136 pub end: usize,
137}
138
139fn null_is_empty<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
140where
141 D: serde::Deserializer<'de>,
142 T: serde::Deserialize<'de>,
143{
144 let opt = Option::deserialize(deserializer)?;
145 match opt {
146 Some(v) => Ok(v),
147 None => Ok(Vec::new()),
148 }
149}
150
151fn null_is_false<'de, D>(deserializer: D) -> Result<bool, D::Error>
152where
153 D: serde::Deserializer<'de>,
154{
155 let opt = Option::deserialize(deserializer)?;
156 match opt {
157 Some(v) => Ok(v),
158 None => Ok(false),
159 }
160}
161
162pub fn run_query(to_run: &str) -> Result<HogQLQueryResult, Error> {
163 let client = &context().client;
164 let request = QueryRequest {
165 query: Query::HogQLQuery {
166 query: to_run.to_string(),
167 },
168 refresh: Some(QueryRefresh::Blocking),
169 };
170
171 let response = client.post("query")?.json(&request).send()?;
172
173 let code = response.status();
174 let body = response.text()?;
175
176 let value: Value = serde_json::from_str(&body)?;
177
178 if !code.is_success() {
179 let error: HogQLQueryErrorResponse = serde_json::from_value(value)?;
180 return Ok(Err(error));
181 }
182
183 let response: HogQLQueryResponse = serde_json::from_value(value)?;
185 Ok(Ok(response))
186}
187
188pub fn check_query(to_run: &str) -> Result<MetadataResponse, Error> {
189 let client = &context().client;
190
191 let query = MetadataQuery {
192 language: MetadataLanguage::HogQL,
193 query: to_run.to_string(),
194 source: None, };
196
197 let query = Query::HogQLMetadata(query);
198
199 let request = QueryRequest {
200 query,
201 refresh: None,
202 };
203
204 let response = client.post("query")?.json(&request).send()?;
205
206 let code = response.status();
207 let body = response.text()?;
208
209 let value: Value = serde_json::from_str(&body)?;
210
211 if !code.is_success() {
212 let error: MetadataResponse = serde_json::from_value(value)?;
213 return Ok(error);
214 }
215
216 let response: MetadataResponse = serde_json::from_value(value)?;
217
218 Ok(response)
219}
220
221impl std::error::Error for HogQLQueryErrorResponse {}
222
223impl std::fmt::Display for HogQLQueryErrorResponse {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 write!(f, "{} ({}): {}", self.error_type, self.code, self.detail)
226 }
227}