1use std::sync::{Arc, Weak};
16
17use arrow_schema::SchemaRef as ArrowSchemaRef;
18use common_catalog::consts::INFORMATION_SCHEMA_STATISTICS_TABLE_ID;
19use common_error::ext::BoxedError;
20use common_recordbatch::adapter::RecordBatchStreamAdapter;
21use common_recordbatch::{RecordBatch, SendableRecordBatchStream};
22use datafusion::execution::TaskContext;
23use datafusion::physical_plan::SendableRecordBatchStream as DfSendableRecordBatchStream;
24use datafusion::physical_plan::stream::RecordBatchStreamAdapter as DfRecordBatchStreamAdapter;
25use datafusion::physical_plan::streaming::PartitionStream as DfPartitionStream;
26use datatypes::prelude::{ConcreteDataType, ScalarVectorBuilder, VectorRef};
27use datatypes::schema::{ColumnSchema, FulltextBackend, Schema, SchemaRef};
28use datatypes::value::Value;
29use datatypes::vectors::{Int64VectorBuilder, StringVectorBuilder};
30use futures::TryStreamExt;
31use snafu::{OptionExt, ResultExt};
32use store_api::sst_entry::{
33 PUFFIN_INDEX_TYPE_BLOOM_FILTER, PUFFIN_INDEX_TYPE_FULLTEXT_BLOOM,
34 PUFFIN_INDEX_TYPE_FULLTEXT_TANTIVY, PUFFIN_INDEX_TYPE_INVERTED,
35};
36use store_api::storage::{ScanRequest, TableId};
37
38use crate::CatalogManager;
39use crate::error::{
40 CreateRecordBatchSnafu, InternalSnafu, Result, UpgradeWeakCatalogManagerRefSnafu,
41};
42use crate::system_schema::SystemTable;
43use crate::system_schema::information_schema::key_column_usage::{
44 CONSTRAINT_NAME_FULLTEXT_INDEX, CONSTRAINT_NAME_INVERTED_INDEX, CONSTRAINT_NAME_PRI,
45 CONSTRAINT_NAME_SKIPPING_INDEX, CONSTRAINT_NAME_TIME_INDEX,
46};
47use crate::system_schema::information_schema::{
48 InformationSchemaTableScope, InformationTable, Predicates, STATISTICS,
49 primary_key_encoding_index_type,
50};
51
52pub const TABLE_CATALOG: &str = "table_catalog";
53pub const TABLE_SCHEMA: &str = "table_schema";
54pub const TABLE_NAME: &str = "table_name";
55pub const NON_UNIQUE: &str = "non_unique";
56pub const INDEX_SCHEMA: &str = "index_schema";
57pub const INDEX_NAME: &str = "index_name";
58pub const SEQ_IN_INDEX: &str = "seq_in_index";
59pub const COLUMN_NAME: &str = "column_name";
60pub const COLLATION: &str = "collation";
61pub const CARDINALITY: &str = "cardinality";
62pub const SUB_PART: &str = "sub_part";
63pub const PACKED: &str = "packed";
64pub const NULLABLE: &str = "nullable";
65pub const INDEX_TYPE: &str = "index_type";
66pub const COMMENT: &str = "comment";
67pub const INDEX_COMMENT: &str = "index_comment";
68pub const IS_VISIBLE: &str = "is_visible";
69pub const EXPRESSION: &str = "expression";
70pub const GREPTIME_INDEX_TYPE: &str = "greptime_index_type";
71
72const INIT_CAPACITY: usize = 42;
73const MYSQL_DEFAULT_CATALOG: &str = "def";
74const ASCENDING_COLLATION: &str = "A";
75const YES: &str = "YES";
76const EMPTY: &str = "";
77const PRIMARY: &str = "PRIMARY";
78const TIME: &str = "TIME";
79const FULLTEXT: &str = "FULLTEXT";
80const INVERTED: &str = "INVERTED";
81const SKIPPING: &str = "SKIPPING";
82const SECONDARY_INDEX_SEQ: i64 = 1;
83
84#[derive(Debug)]
87pub(super) struct InformationSchemaStatistics {
88 schema: SchemaRef,
89 catalog_name: String,
90 catalog_manager: Weak<dyn CatalogManager>,
91}
92
93impl InformationSchemaStatistics {
94 pub(super) fn new(catalog_name: String, catalog_manager: Weak<dyn CatalogManager>) -> Self {
95 Self {
96 schema: Self::schema(),
97 catalog_name,
98 catalog_manager,
99 }
100 }
101
102 pub(crate) fn schema() -> SchemaRef {
103 Arc::new(Schema::new(vec![
104 ColumnSchema::new(TABLE_CATALOG, ConcreteDataType::string_datatype(), false),
105 ColumnSchema::new(TABLE_SCHEMA, ConcreteDataType::string_datatype(), false),
106 ColumnSchema::new(TABLE_NAME, ConcreteDataType::string_datatype(), false),
107 ColumnSchema::new(NON_UNIQUE, ConcreteDataType::int64_datatype(), false),
108 ColumnSchema::new(INDEX_SCHEMA, ConcreteDataType::string_datatype(), false),
109 ColumnSchema::new(INDEX_NAME, ConcreteDataType::string_datatype(), false),
110 ColumnSchema::new(SEQ_IN_INDEX, ConcreteDataType::int64_datatype(), false),
111 ColumnSchema::new(COLUMN_NAME, ConcreteDataType::string_datatype(), false),
112 ColumnSchema::new(COLLATION, ConcreteDataType::string_datatype(), true),
113 ColumnSchema::new(CARDINALITY, ConcreteDataType::int64_datatype(), true),
114 ColumnSchema::new(SUB_PART, ConcreteDataType::int64_datatype(), true),
115 ColumnSchema::new(PACKED, ConcreteDataType::string_datatype(), true),
116 ColumnSchema::new(NULLABLE, ConcreteDataType::string_datatype(), false),
117 ColumnSchema::new(INDEX_TYPE, ConcreteDataType::string_datatype(), false),
118 ColumnSchema::new(COMMENT, ConcreteDataType::string_datatype(), false),
119 ColumnSchema::new(INDEX_COMMENT, ConcreteDataType::string_datatype(), false),
120 ColumnSchema::new(IS_VISIBLE, ConcreteDataType::string_datatype(), false),
121 ColumnSchema::new(EXPRESSION, ConcreteDataType::string_datatype(), true),
122 ColumnSchema::new(
123 GREPTIME_INDEX_TYPE,
124 ConcreteDataType::string_datatype(),
125 true,
126 ),
127 ]))
128 }
129
130 fn builder(&self) -> InformationSchemaStatisticsBuilder {
131 InformationSchemaStatisticsBuilder::new(
132 self.schema.clone(),
133 self.catalog_name.clone(),
134 self.catalog_manager.clone(),
135 )
136 }
137}
138
139impl InformationTable for InformationSchemaStatistics {
140 fn scope(&self) -> InformationSchemaTableScope {
141 InformationSchemaTableScope::Catalog
142 }
143}
144
145impl SystemTable for InformationSchemaStatistics {
146 fn table_id(&self) -> TableId {
147 INFORMATION_SCHEMA_STATISTICS_TABLE_ID
148 }
149
150 fn table_name(&self) -> &'static str {
151 STATISTICS
152 }
153
154 fn schema(&self) -> SchemaRef {
155 self.schema.clone()
156 }
157
158 fn to_stream(&self, request: ScanRequest) -> Result<SendableRecordBatchStream> {
159 let schema = self.schema.arrow_schema().clone();
160 let mut builder = self.builder();
161 let stream = Box::pin(DfRecordBatchStreamAdapter::new(
162 schema,
163 futures::stream::once(async move {
164 builder
165 .make_statistics(Some(request))
166 .await
167 .map(|x| x.into_df_record_batch())
168 .map_err(Into::into)
169 }),
170 ));
171 Ok(Box::pin(
172 RecordBatchStreamAdapter::try_new(stream)
173 .map_err(BoxedError::new)
174 .context(InternalSnafu)?,
175 ))
176 }
177}
178
179struct InformationSchemaStatisticsBuilder {
180 schema: SchemaRef,
181 catalog_name: String,
182 catalog_manager: Weak<dyn CatalogManager>,
183
184 table_catalogs: StringVectorBuilder,
185 table_schemas: StringVectorBuilder,
186 table_names: StringVectorBuilder,
187 non_unique: Int64VectorBuilder,
188 index_schemas: StringVectorBuilder,
189 index_names: StringVectorBuilder,
190 seq_in_index: Int64VectorBuilder,
191 column_names: StringVectorBuilder,
192 collations: StringVectorBuilder,
193 cardinalities: Int64VectorBuilder,
194 sub_parts: Int64VectorBuilder,
195 packed: StringVectorBuilder,
196 nullable: StringVectorBuilder,
197 index_types: StringVectorBuilder,
198 comments: StringVectorBuilder,
199 index_comments: StringVectorBuilder,
200 is_visible: StringVectorBuilder,
201 expressions: StringVectorBuilder,
202 greptime_index_types: StringVectorBuilder,
203}
204
205impl InformationSchemaStatisticsBuilder {
206 fn new(
207 schema: SchemaRef,
208 catalog_name: String,
209 catalog_manager: Weak<dyn CatalogManager>,
210 ) -> Self {
211 Self {
212 schema,
213 catalog_name,
214 catalog_manager,
215 table_catalogs: StringVectorBuilder::with_capacity(INIT_CAPACITY),
216 table_schemas: StringVectorBuilder::with_capacity(INIT_CAPACITY),
217 table_names: StringVectorBuilder::with_capacity(INIT_CAPACITY),
218 non_unique: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
219 index_schemas: StringVectorBuilder::with_capacity(INIT_CAPACITY),
220 index_names: StringVectorBuilder::with_capacity(INIT_CAPACITY),
221 seq_in_index: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
222 column_names: StringVectorBuilder::with_capacity(INIT_CAPACITY),
223 collations: StringVectorBuilder::with_capacity(INIT_CAPACITY),
224 cardinalities: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
225 sub_parts: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
226 packed: StringVectorBuilder::with_capacity(INIT_CAPACITY),
227 nullable: StringVectorBuilder::with_capacity(INIT_CAPACITY),
228 index_types: StringVectorBuilder::with_capacity(INIT_CAPACITY),
229 comments: StringVectorBuilder::with_capacity(INIT_CAPACITY),
230 index_comments: StringVectorBuilder::with_capacity(INIT_CAPACITY),
231 is_visible: StringVectorBuilder::with_capacity(INIT_CAPACITY),
232 expressions: StringVectorBuilder::with_capacity(INIT_CAPACITY),
233 greptime_index_types: StringVectorBuilder::with_capacity(INIT_CAPACITY),
234 }
235 }
236
237 async fn make_statistics(&mut self, request: Option<ScanRequest>) -> Result<RecordBatch> {
238 let catalog_name = self.catalog_name.clone();
239 let catalog_manager = self
240 .catalog_manager
241 .upgrade()
242 .context(UpgradeWeakCatalogManagerRefSnafu)?;
243 let predicates = Predicates::from_scan_request(&request);
244
245 for schema_name in catalog_manager.schema_names(&catalog_name, None).await? {
246 let mut stream = catalog_manager.tables(&catalog_name, &schema_name, None);
247
248 while let Some(table) = stream.try_next().await? {
249 let table_info = table.table_info();
250 let table_name = &table_info.name;
251 let keys = &table_info.meta.primary_key_indices;
252 let schema = table.schema();
253 let primary_key_encoding =
254 primary_key_encoding_index_type(&table_info.meta.options.extra_options);
255
256 for (primary_key_seq, idx) in keys.iter().enumerate() {
257 if let Some(column) = schema.column_schemas().get(*idx) {
258 self.add_statistics(
259 &predicates,
260 &schema_name,
261 table_name,
262 0,
263 CONSTRAINT_NAME_PRI,
264 primary_key_seq as i64 + 1,
265 column,
266 PRIMARY,
267 primary_key_encoding,
268 );
269 }
270 }
271
272 for column in schema.column_schemas() {
273 if column.is_time_index() {
274 self.add_statistics(
275 &predicates,
276 &schema_name,
277 table_name,
278 1,
279 CONSTRAINT_NAME_TIME_INDEX,
280 1,
281 column,
282 TIME,
283 EMPTY,
284 );
285 }
286 if column.is_inverted_indexed() {
287 let index_name =
288 column_index_name(CONSTRAINT_NAME_INVERTED_INDEX, &column.name);
289 self.add_statistics(
290 &predicates,
291 &schema_name,
292 table_name,
293 1,
294 &index_name,
295 SECONDARY_INDEX_SEQ,
296 column,
297 INVERTED,
298 PUFFIN_INDEX_TYPE_INVERTED,
299 );
300 }
301 if let Ok(Some(options)) = column.fulltext_options()
302 && options.enable
303 {
304 let greptime_index_type = match options.backend {
305 FulltextBackend::Bloom => PUFFIN_INDEX_TYPE_FULLTEXT_BLOOM,
306 FulltextBackend::Tantivy => PUFFIN_INDEX_TYPE_FULLTEXT_TANTIVY,
307 };
308 let index_name =
309 column_index_name(CONSTRAINT_NAME_FULLTEXT_INDEX, &column.name);
310 self.add_statistics(
311 &predicates,
312 &schema_name,
313 table_name,
314 1,
315 &index_name,
316 SECONDARY_INDEX_SEQ,
317 column,
318 FULLTEXT,
319 greptime_index_type,
320 );
321 }
322 if column.is_skipping_indexed() {
323 let index_name =
324 column_index_name(CONSTRAINT_NAME_SKIPPING_INDEX, &column.name);
325 self.add_statistics(
326 &predicates,
327 &schema_name,
328 table_name,
329 1,
330 &index_name,
331 SECONDARY_INDEX_SEQ,
332 column,
333 SKIPPING,
334 PUFFIN_INDEX_TYPE_BLOOM_FILTER,
335 );
336 }
337 }
338 }
339 }
340
341 self.finish()
342 }
343
344 #[allow(clippy::too_many_arguments)]
345 fn add_statistics(
346 &mut self,
347 predicates: &Predicates,
348 table_schema: &str,
349 table_name: &str,
350 non_unique: i64,
351 index_name: &str,
352 seq_in_index: i64,
353 column: &ColumnSchema,
354 index_type: &str,
355 greptime_index_type: &str,
356 ) {
357 let nullable = if column.is_nullable() { YES } else { EMPTY };
360 let row = [
361 (TABLE_CATALOG, &Value::from(MYSQL_DEFAULT_CATALOG)),
362 (TABLE_SCHEMA, &Value::from(table_schema)),
363 (TABLE_NAME, &Value::from(table_name)),
364 (NON_UNIQUE, &Value::from(non_unique)),
365 (INDEX_SCHEMA, &Value::from(table_schema)),
366 (INDEX_NAME, &Value::from(index_name)),
367 (SEQ_IN_INDEX, &Value::from(seq_in_index)),
368 (COLUMN_NAME, &Value::from(column.name.as_str())),
369 (NULLABLE, &Value::from(nullable)),
370 (INDEX_TYPE, &Value::from(index_type)),
371 (GREPTIME_INDEX_TYPE, &Value::from(greptime_index_type)),
372 ];
373
374 if !predicates.eval(&row) {
375 return;
376 }
377
378 self.table_catalogs.push(Some(MYSQL_DEFAULT_CATALOG));
379 self.table_schemas.push(Some(table_schema));
380 self.table_names.push(Some(table_name));
381 self.non_unique.push(Some(non_unique));
382 self.index_schemas.push(Some(table_schema));
383 self.index_names.push(Some(index_name));
384 self.seq_in_index.push(Some(seq_in_index));
385 self.column_names.push(Some(&column.name));
386 self.collations.push(Some(ASCENDING_COLLATION));
387 self.cardinalities.push(None);
388 self.sub_parts.push(None);
389 self.packed.push(None);
390 self.nullable.push(Some(nullable));
391 self.index_types.push(Some(index_type));
392 self.comments.push(Some(EMPTY));
393 self.index_comments.push(Some(EMPTY));
394 self.is_visible.push(Some(YES));
395 self.expressions.push(None);
396 self.greptime_index_types.push(Some(greptime_index_type));
397 }
398
399 fn finish(&mut self) -> Result<RecordBatch> {
400 let columns: Vec<VectorRef> = vec![
401 Arc::new(self.table_catalogs.finish()),
402 Arc::new(self.table_schemas.finish()),
403 Arc::new(self.table_names.finish()),
404 Arc::new(self.non_unique.finish()),
405 Arc::new(self.index_schemas.finish()),
406 Arc::new(self.index_names.finish()),
407 Arc::new(self.seq_in_index.finish()),
408 Arc::new(self.column_names.finish()),
409 Arc::new(self.collations.finish()),
410 Arc::new(self.cardinalities.finish()),
411 Arc::new(self.sub_parts.finish()),
412 Arc::new(self.packed.finish()),
413 Arc::new(self.nullable.finish()),
414 Arc::new(self.index_types.finish()),
415 Arc::new(self.comments.finish()),
416 Arc::new(self.index_comments.finish()),
417 Arc::new(self.is_visible.finish()),
418 Arc::new(self.expressions.finish()),
419 Arc::new(self.greptime_index_types.finish()),
420 ];
421 RecordBatch::new(self.schema.clone(), columns).context(CreateRecordBatchSnafu)
422 }
423}
424
425fn column_index_name(index_name: &str, column_name: &str) -> String {
426 format!("{}_{}", index_name.replace(' ', "_"), column_name)
427}
428
429impl DfPartitionStream for InformationSchemaStatistics {
430 fn schema(&self) -> &ArrowSchemaRef {
431 self.schema.arrow_schema()
432 }
433
434 fn execute(&self, _: Arc<TaskContext>) -> DfSendableRecordBatchStream {
435 let schema = self.schema.arrow_schema().clone();
436 let mut builder = self.builder();
437 Box::pin(DfRecordBatchStreamAdapter::new(
438 schema,
439 futures::stream::once(async move {
440 builder
441 .make_statistics(None)
442 .await
443 .map(|x| x.into_df_record_batch())
444 .map_err(Into::into)
445 }),
446 ))
447 }
448}