1use std::collections::BTreeMap;
33use std::sync::{Arc, Weak};
34
35use arrow_schema::SchemaRef as ArrowSchemaRef;
36use common_catalog::consts::INFORMATION_SCHEMA_TABLE_SEMANTICS_TABLE_ID;
37use common_error::ext::BoxedError;
38use common_recordbatch::adapter::RecordBatchStreamAdapter;
39use common_recordbatch::{RecordBatch, SendableRecordBatchStream};
40use datafusion::execution::TaskContext;
41use datafusion::physical_plan::SendableRecordBatchStream as DfSendableRecordBatchStream;
42use datafusion::physical_plan::stream::RecordBatchStreamAdapter as DfRecordBatchStreamAdapter;
43use datafusion::physical_plan::streaming::PartitionStream as DfPartitionStream;
44use datatypes::prelude::{ConcreteDataType, ScalarVectorBuilder, VectorRef};
45use datatypes::schema::{ColumnSchema, Schema, SchemaRef};
46use datatypes::value::Value;
47use datatypes::vectors::{StringVectorBuilder, UInt32VectorBuilder};
48use futures::TryStreamExt;
49use serde::Serialize;
50use snafu::{OptionExt, ResultExt};
51use store_api::storage::{ScanRequest, TableId};
52use table::metadata::TableInfo;
53use table::requests::{
54 SEMANTIC_METRIC_METADATA_QUALITY, SEMANTIC_PIPELINE, SEMANTIC_PREFIX, SEMANTIC_SIGNAL_TYPE,
55 SEMANTIC_SOURCE, SEMANTIC_SOURCE_VERSION, is_semantic_option_key,
56};
57
58use crate::CatalogManager;
59use crate::error::{
60 CreateRecordBatchSnafu, InternalSnafu, Result, UpgradeWeakCatalogManagerRefSnafu,
61};
62use crate::system_schema::SystemTable;
63use crate::system_schema::information_schema::{
64 InformationSchemaTableScope, InformationTable, Predicates, TABLE_SEMANTICS,
65};
66use crate::system_schema::semantic_graph::{EntityGraphProviderRef, TableEntityDeclaration};
67use crate::system_schema::utils;
68
69pub const TABLE_CATALOG: &str = "table_catalog";
70pub const TABLE_SCHEMA: &str = "table_schema";
71pub const TABLE_NAME: &str = "table_name";
72pub const TABLE_ID: &str = "table_id";
73pub const SIGNAL_TYPE: &str = "signal_type";
74pub const SOURCE: &str = "source";
75pub const SOURCE_VERSION: &str = "source_version";
76pub const PIPELINE: &str = "pipeline";
77pub const METADATA_QUALITY: &str = "metadata_quality";
78pub const SEMANTIC_OPTIONS: &str = "semantic_options";
79pub const ENTITY_DECLARATIONS: &str = "entity_declarations";
80
81const INIT_CAPACITY: usize = 42;
82
83fn optional_value(v: Option<&str>) -> Value {
84 v.map(Value::from).unwrap_or(Value::Null)
85}
86
87#[derive(Serialize)]
89struct DeclarationEntry {
90 entity_type: String,
91 origin: &'static str,
92 id: Vec<String>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 id_qualifier: Option<String>,
95 #[serde(skip_serializing_if = "Vec::is_empty")]
96 superseded_by: Vec<String>,
97 #[serde(skip_serializing_if = "Vec::is_empty")]
98 descriptive: Vec<String>,
99 #[serde(skip_serializing_if = "Vec::is_empty")]
100 scope: Vec<String>,
101}
102
103impl From<TableEntityDeclaration> for DeclarationEntry {
104 fn from(declaration: TableEntityDeclaration) -> Self {
105 Self {
106 entity_type: declaration.entity_type,
107 origin: declaration.origin.as_str(),
108 id: declaration.id_columns,
109 id_qualifier: declaration.id_qualifier,
110 superseded_by: declaration.superseded_by_columns,
111 descriptive: declaration.descriptive_columns,
112 scope: declaration.scope_columns,
113 }
114 }
115}
116
117fn entity_declarations_json(declarations: Vec<TableEntityDeclaration>) -> Option<String> {
118 if declarations.is_empty() {
119 return None;
120 }
121 let entries: Vec<DeclarationEntry> = declarations.into_iter().map(Into::into).collect();
122 serde_json::to_string(&entries).ok()
125}
126
127#[derive(Default)]
130struct SemanticRow<'a> {
131 signal_type: Option<&'a str>,
132 source: Option<&'a str>,
133 source_version: Option<&'a str>,
134 pipeline: Option<&'a str>,
135 metadata_quality: Option<&'a str>,
136 options_json: Option<String>,
137}
138
139impl<'a> SemanticRow<'a> {
140 fn extract(table_info: &'a TableInfo) -> Option<Self> {
143 let mut signal_type = None;
144 let mut source = None;
145 let mut source_version = None;
146 let mut pipeline = None;
147 let mut metadata_quality = None;
148 let mut rest = BTreeMap::new();
149
150 for (key, value) in &table_info.meta.options.extra_options {
151 if !is_semantic_option_key(key) {
152 continue;
153 }
154 match key.as_str() {
155 SEMANTIC_SIGNAL_TYPE => signal_type = Some(value.as_str()),
156 SEMANTIC_SOURCE => source = Some(value.as_str()),
157 SEMANTIC_SOURCE_VERSION => source_version = Some(value.as_str()),
158 SEMANTIC_PIPELINE => pipeline = Some(value.as_str()),
159 SEMANTIC_METRIC_METADATA_QUALITY => metadata_quality = Some(value.as_str()),
160 _ => {
161 let short = key.strip_prefix(SEMANTIC_PREFIX).unwrap_or(key);
162 rest.insert(short, value.as_str());
163 }
164 }
165 }
166
167 let has_any = signal_type.is_some()
168 || source.is_some()
169 || source_version.is_some()
170 || pipeline.is_some()
171 || metadata_quality.is_some()
172 || !rest.is_empty();
173 if !has_any {
174 return None;
175 }
176
177 let options_json = (!rest.is_empty())
181 .then(|| serde_json::to_string(&rest).ok())
182 .flatten();
183
184 Some(Self {
185 signal_type,
186 source,
187 source_version,
188 pipeline,
189 metadata_quality,
190 options_json,
191 })
192 }
193}
194
195#[derive(Debug)]
196pub(super) struct InformationSchemaTableSemantics {
197 schema: SchemaRef,
198 catalog_name: String,
199 catalog_manager: Weak<dyn CatalogManager>,
200}
201
202impl InformationSchemaTableSemantics {
203 pub(super) fn new(catalog_name: String, catalog_manager: Weak<dyn CatalogManager>) -> Self {
204 Self {
205 schema: Self::schema(),
206 catalog_name,
207 catalog_manager,
208 }
209 }
210
211 fn schema() -> SchemaRef {
212 Arc::new(Schema::new(vec![
213 ColumnSchema::new(TABLE_CATALOG, ConcreteDataType::string_datatype(), false),
214 ColumnSchema::new(TABLE_SCHEMA, ConcreteDataType::string_datatype(), false),
215 ColumnSchema::new(TABLE_NAME, ConcreteDataType::string_datatype(), false),
216 ColumnSchema::new(TABLE_ID, ConcreteDataType::uint32_datatype(), false),
217 ColumnSchema::new(SIGNAL_TYPE, ConcreteDataType::string_datatype(), true),
218 ColumnSchema::new(SOURCE, ConcreteDataType::string_datatype(), true),
219 ColumnSchema::new(SOURCE_VERSION, ConcreteDataType::string_datatype(), true),
220 ColumnSchema::new(PIPELINE, ConcreteDataType::string_datatype(), true),
221 ColumnSchema::new(METADATA_QUALITY, ConcreteDataType::string_datatype(), true),
222 ColumnSchema::new(SEMANTIC_OPTIONS, ConcreteDataType::string_datatype(), true),
223 ColumnSchema::new(
224 ENTITY_DECLARATIONS,
225 ConcreteDataType::string_datatype(),
226 true,
227 ),
228 ]))
229 }
230
231 fn builder(&self) -> InformationSchemaSemanticTablesBuilder {
232 InformationSchemaSemanticTablesBuilder::new(
233 self.schema.clone(),
234 self.catalog_name.clone(),
235 self.catalog_manager.clone(),
236 )
237 }
238}
239
240impl InformationTable for InformationSchemaTableSemantics {
241 fn scope(&self) -> InformationSchemaTableScope {
242 InformationSchemaTableScope::Catalog
243 }
244}
245
246impl SystemTable for InformationSchemaTableSemantics {
247 fn table_id(&self) -> TableId {
248 INFORMATION_SCHEMA_TABLE_SEMANTICS_TABLE_ID
249 }
250
251 fn table_name(&self) -> &'static str {
252 TABLE_SEMANTICS
253 }
254
255 fn schema(&self) -> SchemaRef {
256 self.schema.clone()
257 }
258
259 fn to_stream(&self, request: ScanRequest) -> Result<SendableRecordBatchStream> {
260 let schema = self.schema.arrow_schema().clone();
261 let mut builder = self.builder();
262 let stream = Box::pin(DfRecordBatchStreamAdapter::new(
263 schema,
264 futures::stream::once(async move {
265 builder
266 .make_tables(Some(request))
267 .await
268 .map(|x| x.into_df_record_batch())
269 .map_err(|err| datafusion::error::DataFusionError::External(Box::new(err)))
270 }),
271 ));
272 Ok(Box::pin(
273 RecordBatchStreamAdapter::try_new(stream)
274 .map_err(BoxedError::new)
275 .context(InternalSnafu)?,
276 ))
277 }
278}
279
280struct InformationSchemaSemanticTablesBuilder {
281 schema: SchemaRef,
282 catalog_name: String,
283 catalog_manager: Weak<dyn CatalogManager>,
284
285 catalog_names: StringVectorBuilder,
286 schema_names: StringVectorBuilder,
287 table_names: StringVectorBuilder,
288 table_ids: UInt32VectorBuilder,
289 signal_types: StringVectorBuilder,
290 sources: StringVectorBuilder,
291 source_versions: StringVectorBuilder,
292 pipelines: StringVectorBuilder,
293 metadata_qualities: StringVectorBuilder,
294 semantic_options: StringVectorBuilder,
295 entity_declarations: StringVectorBuilder,
296}
297
298impl InformationSchemaSemanticTablesBuilder {
299 fn new(
300 schema: SchemaRef,
301 catalog_name: String,
302 catalog_manager: Weak<dyn CatalogManager>,
303 ) -> Self {
304 Self {
305 schema,
306 catalog_name,
307 catalog_manager,
308 catalog_names: StringVectorBuilder::with_capacity(INIT_CAPACITY),
309 schema_names: StringVectorBuilder::with_capacity(INIT_CAPACITY),
310 table_names: StringVectorBuilder::with_capacity(INIT_CAPACITY),
311 table_ids: UInt32VectorBuilder::with_capacity(INIT_CAPACITY),
312 signal_types: StringVectorBuilder::with_capacity(INIT_CAPACITY),
313 sources: StringVectorBuilder::with_capacity(INIT_CAPACITY),
314 source_versions: StringVectorBuilder::with_capacity(INIT_CAPACITY),
315 pipelines: StringVectorBuilder::with_capacity(INIT_CAPACITY),
316 metadata_qualities: StringVectorBuilder::with_capacity(INIT_CAPACITY),
317 semantic_options: StringVectorBuilder::with_capacity(INIT_CAPACITY),
318 entity_declarations: StringVectorBuilder::with_capacity(INIT_CAPACITY),
319 }
320 }
321
322 async fn make_tables(&mut self, request: Option<ScanRequest>) -> Result<RecordBatch> {
323 let catalog_name = self.catalog_name.clone();
324 let catalog_manager = self
325 .catalog_manager
326 .upgrade()
327 .context(UpgradeWeakCatalogManagerRefSnafu)?;
328 let predicates = Predicates::from_scan_request(&request);
329 let graph_provider = utils::entity_graph_provider(&self.catalog_manager)?;
332
333 for schema_name in catalog_manager.schema_names(&catalog_name, None).await? {
334 let mut table_stream = catalog_manager.tables(&catalog_name, &schema_name, None);
335 while let Some(table) = table_stream.try_next().await? {
336 self.add_table(
337 &predicates,
338 &catalog_name,
339 &schema_name,
340 table.table_info(),
341 graph_provider.as_ref(),
342 );
343 }
344 }
345
346 self.finish()
347 }
348
349 fn add_table(
350 &mut self,
351 predicates: &Predicates,
352 catalog_name: &str,
353 schema_name: &str,
354 table_info: Arc<TableInfo>,
355 graph_provider: Option<&EntityGraphProviderRef>,
356 ) {
357 let semantic_row = SemanticRow::extract(&table_info);
358 let carries_options = semantic_row.is_some();
359 let row = semantic_row.unwrap_or_default();
360
361 let table_name = table_info.name.as_ref();
362 let catalog_v = Value::from(catalog_name);
363 let schema_v = Value::from(schema_name);
364 let name_v = Value::from(table_name);
365 let signal_v = optional_value(row.signal_type);
366 let source_v = optional_value(row.source);
367 let source_version_v = optional_value(row.source_version);
368 let pipeline_v = optional_value(row.pipeline);
369 let quality_v = optional_value(row.metadata_quality);
370 let predicate_row = [
371 (TABLE_CATALOG, &catalog_v),
372 (TABLE_SCHEMA, &schema_v),
373 (TABLE_NAME, &name_v),
374 (SIGNAL_TYPE, &signal_v),
375 (SOURCE, &source_v),
376 (SOURCE_VERSION, &source_version_v),
377 (PIPELINE, &pipeline_v),
378 (METADATA_QUALITY, &quality_v),
379 ];
380 if !predicates.eval(&predicate_row) {
381 return;
382 }
383
384 let declarations_json = graph_provider
385 .map(|provider| provider.table_declarations(&table_info))
386 .and_then(entity_declarations_json);
387 if !carries_options && declarations_json.is_none() {
388 return;
389 }
390
391 self.catalog_names.push(Some(catalog_name));
392 self.schema_names.push(Some(schema_name));
393 self.table_names.push(Some(table_name));
394 self.table_ids.push(Some(table_info.table_id()));
395 self.signal_types.push(row.signal_type);
396 self.sources.push(row.source);
397 self.source_versions.push(row.source_version);
398 self.pipelines.push(row.pipeline);
399 self.metadata_qualities.push(row.metadata_quality);
400 self.semantic_options.push(row.options_json.as_deref());
401 self.entity_declarations.push(declarations_json.as_deref());
402 }
403
404 fn finish(&mut self) -> Result<RecordBatch> {
405 let columns: Vec<VectorRef> = vec![
406 Arc::new(self.catalog_names.finish()),
407 Arc::new(self.schema_names.finish()),
408 Arc::new(self.table_names.finish()),
409 Arc::new(self.table_ids.finish()),
410 Arc::new(self.signal_types.finish()),
411 Arc::new(self.sources.finish()),
412 Arc::new(self.source_versions.finish()),
413 Arc::new(self.pipelines.finish()),
414 Arc::new(self.metadata_qualities.finish()),
415 Arc::new(self.semantic_options.finish()),
416 Arc::new(self.entity_declarations.finish()),
417 ];
418 RecordBatch::new(self.schema.clone(), columns).context(CreateRecordBatchSnafu)
419 }
420}
421
422impl DfPartitionStream for InformationSchemaTableSemantics {
423 fn schema(&self) -> &ArrowSchemaRef {
424 self.schema.arrow_schema()
425 }
426
427 fn execute(&self, _: Arc<TaskContext>) -> DfSendableRecordBatchStream {
428 let schema = self.schema.arrow_schema().clone();
429 let mut builder = self.builder();
430 Box::pin(DfRecordBatchStreamAdapter::new(
431 schema,
432 futures::stream::once(async move {
433 builder
434 .make_tables(None)
435 .await
436 .map(|x| x.into_df_record_batch())
437 .map_err(Into::into)
438 }),
439 ))
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use std::collections::HashMap;
446
447 use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, MITO_ENGINE};
448 use datatypes::schema::SchemaBuilder;
449 use table::metadata::{TableInfoBuilder, TableMeta, TableType};
450 use table::requests::{
451 SEMANTIC_METRIC_TYPE, SEMANTIC_METRIC_UNIT, SEMANTIC_SOURCE_VERSION, TableOptions,
452 };
453
454 use super::*;
455
456 fn table_info(extra: &[(&str, &str)]) -> TableInfo {
457 let schema = Arc::new(
458 SchemaBuilder::try_from_columns(vec![ColumnSchema::new(
459 "ts",
460 ConcreteDataType::timestamp_millisecond_datatype(),
461 false,
462 )])
463 .unwrap()
464 .build()
465 .unwrap(),
466 );
467 let options = TableOptions {
468 extra_options: extra
469 .iter()
470 .map(|(k, v)| (k.to_string(), v.to_string()))
471 .collect::<HashMap<_, _>>(),
472 ..Default::default()
473 };
474 let meta = TableMeta {
475 schema,
476 primary_key_indices: vec![],
477 value_indices: vec![],
478 engine: MITO_ENGINE.to_string(),
479 next_column_id: 1,
480 options,
481 created_on: Default::default(),
482 updated_on: Default::default(),
483 partition_key_indices: vec![],
484 column_ids: vec![],
485 };
486 TableInfoBuilder::default()
487 .table_id(1)
488 .name("t")
489 .catalog_name(DEFAULT_CATALOG_NAME)
490 .schema_name(DEFAULT_SCHEMA_NAME)
491 .table_version(0)
492 .table_type(TableType::Base)
493 .meta(meta)
494 .build()
495 .unwrap()
496 }
497
498 #[test]
499 fn extract_promotes_core_keys_and_folds_the_rest() {
500 let info = table_info(&[
501 (SEMANTIC_SIGNAL_TYPE, "metric"),
502 (SEMANTIC_SOURCE, "opentelemetry"),
503 (SEMANTIC_SOURCE_VERSION, "2.0"),
504 (SEMANTIC_PIPELINE, "greptime_metric_v1"),
505 (SEMANTIC_METRIC_METADATA_QUALITY, "declared"),
506 (SEMANTIC_METRIC_TYPE, "counter"),
507 (SEMANTIC_METRIC_UNIT, "By"),
508 ("ttl", "7d"),
510 ]);
511
512 let row = SemanticRow::extract(&info).unwrap();
513 assert_eq!(row.signal_type, Some("metric"));
514 assert_eq!(row.source, Some("opentelemetry"));
515 assert_eq!(row.source_version, Some("2.0"));
516 assert_eq!(row.pipeline, Some("greptime_metric_v1"));
517 assert_eq!(row.metadata_quality, Some("declared"));
518 assert_eq!(
521 row.options_json.as_deref(),
522 Some(r#"{"metric.type":"counter","metric.unit":"By"}"#)
523 );
524 }
525
526 #[test]
527 fn extract_system_table_scope() {
528 let info = table_info(&[(table::requests::SEMANTIC_TABLE_SCOPE, "catalog")]);
529 let row = SemanticRow::extract(&info).unwrap();
530 assert_eq!(
531 row.options_json.as_deref(),
532 Some(r#"{"table_scope":"catalog"}"#)
533 );
534 assert!(row.signal_type.is_none());
535 }
536
537 #[test]
538 fn extract_skips_untagged_table() {
539 let info = table_info(&[("ttl", "7d")]);
540 assert!(SemanticRow::extract(&info).is_none());
541 }
542
543 #[test]
544 fn extract_omits_json_when_only_core_keys_present() {
545 let info = table_info(&[(SEMANTIC_SIGNAL_TYPE, "log")]);
546 let row = SemanticRow::extract(&info).unwrap();
547 assert_eq!(row.signal_type, Some("log"));
548 assert!(row.source.is_none());
549 assert!(row.options_json.is_none());
550 }
551
552 #[test]
553 fn extract_folds_entity_keys_into_json_tail() {
554 let info = table_info(&[
557 (SEMANTIC_SIGNAL_TYPE, "trace"),
558 ("greptime.semantic.entity.service.id", "service_name"),
559 ("greptime.semantic.entity.host.id", "host_id"),
560 ]);
561 let row = SemanticRow::extract(&info).unwrap();
562 assert_eq!(row.signal_type, Some("trace"));
563 assert_eq!(
564 row.options_json.as_deref(),
565 Some(r#"{"entity.host.id":"host_id","entity.service.id":"service_name"}"#)
566 );
567 }
568}