1use std::collections::HashMap;
16use std::sync::Arc;
17
18use api::v1::value::ValueData;
19use api::v1::{
20 ColumnDataType, ColumnDef, ColumnSchema as PbColumnSchema, Row, RowInsertRequest,
21 RowInsertRequests, Rows, SemanticType,
22};
23use async_trait::async_trait;
24use auth::{DASHBOARD_DELETE, DASHBOARD_QUERY, DASHBOARD_SAVE, PermissionReq};
25use common_catalog::consts::{DEFAULT_PRIVATE_SCHEMA_NAME, default_engine};
26use common_error::ext::BoxedError;
27use common_meta::rpc::ddl::TriggerReason;
28use common_query::OutputData;
29use common_recordbatch::util as record_util;
30use common_telemetry::info;
31use common_time::FOREVER;
32use datafusion::datasource::DefaultTableSource;
33use datafusion::logical_expr::col;
34use datafusion::sql::TableReference;
35use datafusion_expr::{DmlStatement, LogicalPlan, lit};
36use datatypes::arrow::array::{Array, AsArray};
37use servers::error::{
38 CollectRecordbatchSnafu, DataFusionSnafu, ExecuteQuerySnafu, NotSupportedSnafu,
39 TableNotFoundSnafu,
40};
41use servers::query_handler::DashboardDefinition;
42use session::context::{QueryContextBuilder, QueryContextRef};
43use snafu::{OptionExt, ResultExt};
44use table::TableRef;
45use table::metadata::TableInfo;
46use table::requests::TTL_KEY;
47use table::table::adapter::DfTableProviderAdapter;
48
49use crate::instance::Instance;
50
51pub const DASHBOARD_TABLE_NAME: &str = "dashboard";
52pub const DASHBOARD_TABLE_NAME_COLUMN_NAME: &str = "name";
53pub const DASHBOARD_TABLE_DEFINITION_COLUMN_NAME: &str = "definition";
54pub const DASHBOARD_TABLE_CREATED_AT_COLUMN_NAME: &str = "created_at";
55
56impl Instance {
57 fn build_dashboard_schema() -> (String, Vec<String>, Vec<ColumnDef>) {
60 (
61 DASHBOARD_TABLE_CREATED_AT_COLUMN_NAME.to_string(),
62 vec![DASHBOARD_TABLE_NAME_COLUMN_NAME.to_string()],
63 vec![
64 ColumnDef {
65 name: DASHBOARD_TABLE_NAME_COLUMN_NAME.to_string(),
66 data_type: ColumnDataType::String as i32,
67 is_nullable: false,
68 default_constraint: vec![],
69 semantic_type: SemanticType::Tag as i32,
70 comment: String::new(),
71 datatype_extension: None,
72 options: None,
73 },
74 ColumnDef {
75 name: DASHBOARD_TABLE_DEFINITION_COLUMN_NAME.to_string(),
76 data_type: ColumnDataType::String as i32,
77 is_nullable: false,
78 default_constraint: vec![],
79 semantic_type: SemanticType::Field as i32,
80 comment: String::new(),
81 datatype_extension: None,
82 options: None,
83 },
84 ColumnDef {
85 name: DASHBOARD_TABLE_CREATED_AT_COLUMN_NAME.to_string(),
86 data_type: ColumnDataType::TimestampNanosecond as i32,
87 is_nullable: false,
88 default_constraint: vec![],
89 semantic_type: SemanticType::Timestamp as i32,
90 comment: String::new(),
91 datatype_extension: None,
92 options: None,
93 },
94 ],
95 )
96 }
97
98 fn build_dashboard_insert_column_schemas() -> Vec<PbColumnSchema> {
100 vec![
101 PbColumnSchema {
102 column_name: DASHBOARD_TABLE_NAME_COLUMN_NAME.to_string(),
103 datatype: ColumnDataType::String.into(),
104 semantic_type: SemanticType::Tag.into(),
105 ..Default::default()
106 },
107 PbColumnSchema {
108 column_name: DASHBOARD_TABLE_DEFINITION_COLUMN_NAME.to_string(),
109 datatype: ColumnDataType::String.into(),
110 semantic_type: SemanticType::Field.into(),
111 ..Default::default()
112 },
113 PbColumnSchema {
114 column_name: DASHBOARD_TABLE_CREATED_AT_COLUMN_NAME.to_string(),
115 datatype: ColumnDataType::TimestampNanosecond.into(),
116 semantic_type: SemanticType::Timestamp.into(),
117 ..Default::default()
118 },
119 ]
120 }
121
122 fn dashboard_query_ctx(table_info: &TableInfo) -> QueryContextRef {
123 QueryContextBuilder::default()
124 .current_catalog(table_info.catalog_name.clone())
125 .current_schema(table_info.schema_name.clone())
126 .build()
127 .into()
128 }
129
130 async fn create_dashboard_table_if_not_exists(
131 &self,
132 ctx: QueryContextRef,
133 ) -> servers::error::Result<TableRef> {
134 let catalog = ctx.current_catalog();
135
136 if let Some(table) = self
137 .catalog_manager
138 .table(
139 catalog,
140 DEFAULT_PRIVATE_SCHEMA_NAME,
141 DASHBOARD_TABLE_NAME,
142 Some(&ctx),
143 )
144 .await?
145 {
146 return Ok(table);
147 }
148
149 let (time_index, primary_keys, column_defs) = Self::build_dashboard_schema();
150
151 let mut table_options = HashMap::new();
152 table_options.insert(TTL_KEY.to_string(), FOREVER.to_string());
153
154 let mut create_table_expr = api::v1::CreateTableExpr {
155 catalog_name: catalog.to_string(),
156 schema_name: DEFAULT_PRIVATE_SCHEMA_NAME.to_string(),
157 table_name: DASHBOARD_TABLE_NAME.to_string(),
158 desc: "GreptimeDB dashboard table".to_string(),
159 column_defs,
160 time_index,
161 primary_keys,
162 create_if_not_exists: true,
163 table_options,
164 table_id: None,
165 engine: default_engine().to_string(),
166 };
167
168 self.statement_executor
169 .create_table_inner(
170 &mut create_table_expr,
171 None,
172 ctx.clone(),
173 TriggerReason::AutoCreate,
174 )
175 .await
176 .map_err(BoxedError::new)
177 .context(ExecuteQuerySnafu)?;
178
179 let table = self
180 .catalog_manager
181 .table(
182 catalog,
183 DEFAULT_PRIVATE_SCHEMA_NAME,
184 DASHBOARD_TABLE_NAME,
185 Some(&ctx),
186 )
187 .await?
188 .context(TableNotFoundSnafu {
189 catalog: catalog.to_string(),
190 schema: DEFAULT_PRIVATE_SCHEMA_NAME.to_string(),
191 table: DASHBOARD_TABLE_NAME.to_string(),
192 })?;
193
194 Ok(table)
195 }
196
197 async fn insert_dashboard(
199 &self,
200 name: &str,
201 definition: &str,
202 query_ctx: QueryContextRef,
203 ) -> servers::error::Result<()> {
204 let table = self
205 .create_dashboard_table_if_not_exists(query_ctx.clone())
206 .await?;
207 let table_info = table.table_info();
208
209 let insert = RowInsertRequest {
210 table_name: DASHBOARD_TABLE_NAME.to_string(),
211 rows: Some(Rows {
212 schema: Self::build_dashboard_insert_column_schemas(),
213 rows: vec![Row {
214 values: vec![
215 ValueData::StringValue(name.to_string()).into(),
216 ValueData::StringValue(definition.to_string()).into(),
217 ValueData::TimestampNanosecondValue(0).into(),
218 ],
219 }],
220 }),
221 };
222
223 let requests = RowInsertRequests {
224 inserts: vec![insert],
225 };
226
227 let output = self
228 .inserter
229 .handle_row_inserts(
230 requests,
231 Self::dashboard_query_ctx(&table_info),
232 &self.statement_executor,
233 false,
234 false,
235 )
236 .await
237 .map_err(BoxedError::new)
238 .context(ExecuteQuerySnafu)?;
239
240 info!(
241 "Insert dashboard success, name: {}, table: {}, output: {:?}",
242 name,
243 table_info.full_table_name(),
244 output
245 );
246
247 Ok(())
248 }
249
250 async fn list_dashboards(
252 &self,
253 query_ctx: QueryContextRef,
254 ) -> servers::error::Result<Vec<DashboardDefinition>> {
255 let table = if let Some(table) = self
256 .catalog_manager
257 .table(
258 query_ctx.current_catalog(),
259 DEFAULT_PRIVATE_SCHEMA_NAME,
260 DASHBOARD_TABLE_NAME,
261 Some(&query_ctx),
262 )
263 .await?
264 {
265 table
266 } else {
267 return Ok(vec![]);
268 };
269
270 let table_info = table.table_info();
271
272 let dataframe = self
273 .query_engine
274 .read_table(table.clone())
275 .map_err(BoxedError::new)
276 .context(ExecuteQuerySnafu)?;
277
278 let dataframe = dataframe
279 .select_columns(&[
280 DASHBOARD_TABLE_NAME_COLUMN_NAME,
281 DASHBOARD_TABLE_DEFINITION_COLUMN_NAME,
282 ])
283 .context(DataFusionSnafu)?;
284
285 let plan = dataframe.into_parts().1;
286
287 let output = self
288 .query_engine
289 .execute(plan, Self::dashboard_query_ctx(&table_info))
290 .await
291 .map_err(BoxedError::new)
292 .context(ExecuteQuerySnafu)?;
293 let output = output
294 .map_dictionary_to_values()
295 .context(CollectRecordbatchSnafu)?;
296
297 let stream = match output.data {
298 OutputData::Stream(stream) => stream,
299 OutputData::RecordBatches(record_batches) => record_batches.as_stream(),
300 _ => unreachable!(),
301 };
302
303 let records = record_util::collect(stream)
304 .await
305 .context(CollectRecordbatchSnafu)?;
306
307 let mut dashboards = Vec::new();
308
309 for r in &records {
310 let name_column = r.column(0);
311 let definition_column = r.column(1);
312
313 let name = name_column
314 .as_string_opt::<i32>()
315 .context(NotSupportedSnafu {
316 feat: "Invalid data type for greptime_private.dashboard.name",
317 })?;
318
319 let definition =
320 definition_column
321 .as_string_opt::<i32>()
322 .context(NotSupportedSnafu {
323 feat: "Invalid data type for greptime_private.dashboard.definition",
324 })?;
325
326 for i in 0..name.len() {
327 dashboards.push(DashboardDefinition {
328 name: name.value(i).to_string(),
329 definition: definition.value(i).to_string(),
330 });
331 }
332 }
333
334 Ok(dashboards)
335 }
336
337 async fn delete_dashboard(
339 &self,
340 name: &str,
341 query_ctx: QueryContextRef,
342 ) -> servers::error::Result<()> {
343 let table = self
344 .create_dashboard_table_if_not_exists(query_ctx.clone())
345 .await?;
346 let table_info = table.table_info();
347
348 let dataframe = self
349 .query_engine
350 .read_table(table.clone())
351 .map_err(BoxedError::new)
352 .context(ExecuteQuerySnafu)?;
353
354 let name_condition = col(DASHBOARD_TABLE_NAME_COLUMN_NAME).eq(lit(name));
355
356 let dataframe = dataframe.filter(name_condition).context(DataFusionSnafu)?;
357
358 let table_name = TableReference::full(
359 table_info.catalog_name.clone(),
360 table_info.schema_name.clone(),
361 table_info.name.clone(),
362 );
363
364 let table_provider = Arc::new(DfTableProviderAdapter::new(table.clone()));
365 let table_source = Arc::new(DefaultTableSource::new(table_provider));
366
367 let stmt = DmlStatement::new(
368 table_name,
369 table_source,
370 datafusion_expr::WriteOp::Delete,
371 Arc::new(dataframe.into_parts().1),
372 );
373
374 let plan = LogicalPlan::Dml(stmt);
375
376 let output = self
377 .query_engine
378 .execute(plan, Self::dashboard_query_ctx(&table_info))
379 .await
380 .map_err(BoxedError::new)
381 .context(ExecuteQuerySnafu)?;
382
383 info!(
384 "Delete dashboard success, name: {}, table: {}, output: {:?}",
385 name,
386 table_info.full_table_name(),
387 output
388 );
389
390 Ok(())
391 }
392}
393
394#[async_trait]
395impl servers::query_handler::DashboardHandler for Instance {
396 async fn save(
397 &self,
398 name: &str,
399 definition: &str,
400 ctx: QueryContextRef,
401 ) -> servers::error::Result<()> {
402 self.check_permission(&ctx, PermissionReq::Action(DASHBOARD_SAVE))?;
403 self.insert_dashboard(name, definition, ctx).await
404 }
405
406 async fn list(&self, ctx: QueryContextRef) -> servers::error::Result<Vec<DashboardDefinition>> {
407 self.check_permission(&ctx, PermissionReq::Action(DASHBOARD_QUERY))?;
408 self.list_dashboards(ctx).await
409 }
410
411 async fn delete(&self, name: &str, ctx: QueryContextRef) -> servers::error::Result<()> {
412 self.check_permission(&ctx, PermissionReq::Action(DASHBOARD_DELETE))?;
413 self.delete_dashboard(name, ctx).await
414 }
415}