Skip to main content

query/
sql.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod show_create_table;
16
17use std::collections::HashMap;
18use std::ops::ControlFlow;
19use std::sync::Arc;
20
21use catalog::CatalogManagerRef;
22use catalog::information_schema::{
23    CHARACTER_SETS, COLLATIONS, COLUMNS, FLOW_STATISTICS, FLOWS, REGION_PEERS, SCHEMATA,
24    STATISTICS, TABLES, VIEWS, columns, flow_statistics, flows, process_list, region_peers,
25    schemata, statistics, tables,
26};
27use common_catalog::consts::{
28    INFORMATION_SCHEMA_NAME, SEMANTIC_TYPE_FIELD, SEMANTIC_TYPE_PRIMARY_KEY,
29    SEMANTIC_TYPE_TIME_INDEX,
30};
31use common_catalog::format_full_table_name;
32use common_datasource::file_format::{FileFormat, Format, infer_schemas};
33use common_datasource::lister::{Lister, Source};
34use common_datasource::object_store::{LocalFileAccess, build_backend_with_path};
35use common_error::ext::BoxedError;
36use common_meta::SchemaOptions;
37use common_meta::ddl::create_flow::{FlowType, effective_eval_schedule_from_flow_info};
38use common_meta::key::flow::flow_info::FlowInfoValue;
39use common_query::Output;
40use common_query::prelude::greptime_timestamp;
41use common_recordbatch::RecordBatches;
42use common_recordbatch::adapter::RecordBatchStreamAdapter;
43use common_time::Timestamp;
44use common_time::timezone::get_timezone;
45use datafusion::dataframe::DataFrame;
46use datafusion::prelude::SessionContext;
47use datafusion_expr::{Expr, SortExpr, col, lit};
48use datatypes::prelude::*;
49use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema, Schema};
50use datatypes::vectors::StringVector;
51use itertools::Itertools;
52use object_store::ObjectStore;
53use once_cell::sync::Lazy;
54use regex::Regex;
55use session::context::{Channel, QueryContextRef};
56pub use show_create_table::create_table_stmt;
57use snafu::{OptionExt, ResultExt, ensure};
58use sql::ast::{Ident, visit_expressions_mut};
59use sql::parser::ParserContext;
60use sql::statements::OptionMap;
61use sql::statements::create::{CreateDatabase, CreateFlow, CreateView, Partitions, SqlOrTql};
62use sql::statements::show::{
63    ShowColumns, ShowDatabases, ShowFlowStatus, ShowFlows, ShowIndex, ShowKind, ShowProcessList,
64    ShowRegion, ShowTableStatus, ShowTables, ShowVariables, ShowViews,
65};
66use sql::statements::statement::Statement;
67use sqlparser::ast::ObjectName;
68use store_api::metric_engine_consts::{is_metric_engine, is_metric_engine_internal_column};
69use table::TableRef;
70use table::metadata::TableInfoRef;
71use table::requests::{FILE_TABLE_LOCATION_KEY, FILE_TABLE_PATTERN_KEY};
72
73use crate::QueryEngineRef;
74use crate::error::{self, Result, UnsupportedVariableSnafu};
75use crate::planner::DfLogicalPlanner;
76
77const SCHEMAS_COLUMN: &str = "Database";
78const OPTIONS_COLUMN: &str = "Options";
79const VIEWS_COLUMN: &str = "Views";
80const FLOWS_COLUMN: &str = "Flows";
81const FIELD_COLUMN: &str = "Field";
82const TABLE_TYPE_COLUMN: &str = "Table_type";
83
84const COLUMN_NAME_COLUMN: &str = "Column";
85const COLUMN_GREPTIME_TYPE_COLUMN: &str = "Greptime_type";
86const COLUMN_TYPE_COLUMN: &str = "Type";
87const COLUMN_KEY_COLUMN: &str = "Key";
88const COLUMN_EXTRA_COLUMN: &str = "Extra";
89const COLUMN_PRIVILEGES_COLUMN: &str = "Privileges";
90const COLUMN_COLLATION_COLUMN: &str = "Collation";
91const COLUMN_NULLABLE_COLUMN: &str = "Null";
92const COLUMN_DEFAULT_COLUMN: &str = "Default";
93const COLUMN_COMMENT_COLUMN: &str = "Comment";
94const COLUMN_SEMANTIC_TYPE_COLUMN: &str = "Semantic Type";
95
96const YES_STR: &str = "YES";
97const NO_STR: &str = "NO";
98const PRI_KEY: &str = "PRI";
99
100/// SHOW index columns
101const INDEX_TABLE_COLUMN: &str = "Table";
102const INDEX_NONT_UNIQUE_COLUMN: &str = "Non_unique";
103const INDEX_CARDINALITY_COLUMN: &str = "Cardinality";
104const INDEX_SUB_PART_COLUMN: &str = "Sub_part";
105const INDEX_PACKED_COLUMN: &str = "Packed";
106const INDEX_INDEX_TYPE_COLUMN: &str = "Index_type";
107const INDEX_COMMENT_COLUMN: &str = "Index_comment";
108const INDEX_VISIBLE_COLUMN: &str = "Visible";
109const INDEX_EXPRESSION_COLUMN: &str = "Expression";
110const INDEX_KEY_NAME_COLUMN: &str = "Key_name";
111const INDEX_SEQ_IN_INDEX_COLUMN: &str = "Seq_in_index";
112const INDEX_COLUMN_NAME_COLUMN: &str = "Column_name";
113
114pub static DESCRIBE_TABLE_OUTPUT_SCHEMA: Lazy<Arc<Schema>> = Lazy::new(|| {
115    Arc::new(Schema::new(vec![
116        ColumnSchema::new(
117            COLUMN_NAME_COLUMN,
118            ConcreteDataType::string_datatype(),
119            false,
120        ),
121        ColumnSchema::new(
122            COLUMN_TYPE_COLUMN,
123            ConcreteDataType::string_datatype(),
124            false,
125        ),
126        ColumnSchema::new(COLUMN_KEY_COLUMN, ConcreteDataType::string_datatype(), true),
127        ColumnSchema::new(
128            COLUMN_NULLABLE_COLUMN,
129            ConcreteDataType::string_datatype(),
130            false,
131        ),
132        ColumnSchema::new(
133            COLUMN_DEFAULT_COLUMN,
134            ConcreteDataType::string_datatype(),
135            false,
136        ),
137        ColumnSchema::new(
138            COLUMN_SEMANTIC_TYPE_COLUMN,
139            ConcreteDataType::string_datatype(),
140            false,
141        ),
142    ]))
143});
144
145static SHOW_CREATE_DATABASE_OUTPUT_SCHEMA: Lazy<Arc<Schema>> = Lazy::new(|| {
146    Arc::new(Schema::new(vec![
147        ColumnSchema::new("Database", ConcreteDataType::string_datatype(), false),
148        ColumnSchema::new(
149            "Create Database",
150            ConcreteDataType::string_datatype(),
151            false,
152        ),
153    ]))
154});
155
156static SHOW_CREATE_TABLE_OUTPUT_SCHEMA: Lazy<Arc<Schema>> = Lazy::new(|| {
157    Arc::new(Schema::new(vec![
158        ColumnSchema::new("Table", ConcreteDataType::string_datatype(), false),
159        ColumnSchema::new("Create Table", ConcreteDataType::string_datatype(), false),
160    ]))
161});
162
163static SHOW_CREATE_FLOW_OUTPUT_SCHEMA: Lazy<Arc<Schema>> = Lazy::new(|| {
164    Arc::new(Schema::new(vec![
165        ColumnSchema::new("Flow", ConcreteDataType::string_datatype(), false),
166        ColumnSchema::new("Create Flow", ConcreteDataType::string_datatype(), false),
167    ]))
168});
169
170static SHOW_CREATE_VIEW_OUTPUT_SCHEMA: Lazy<Arc<Schema>> = Lazy::new(|| {
171    Arc::new(Schema::new(vec![
172        ColumnSchema::new("View", ConcreteDataType::string_datatype(), false),
173        ColumnSchema::new("Create View", ConcreteDataType::string_datatype(), false),
174    ]))
175});
176
177pub async fn show_databases(
178    stmt: ShowDatabases,
179    query_engine: &QueryEngineRef,
180    catalog_manager: &CatalogManagerRef,
181    query_ctx: QueryContextRef,
182) -> Result<Output> {
183    let dataframe =
184        show_databases_dataframe(&stmt, query_engine, catalog_manager, query_ctx).await?;
185    dataframe_to_output(dataframe).await
186}
187
188/// Builds the [`DataFrame`] for `SHOW DATABASES` without executing it.
189pub async fn show_databases_dataframe(
190    stmt: &ShowDatabases,
191    query_engine: &QueryEngineRef,
192    catalog_manager: &CatalogManagerRef,
193    query_ctx: QueryContextRef,
194) -> Result<DataFrame> {
195    let projects = if stmt.full {
196        vec![
197            (schemata::SCHEMA_NAME, SCHEMAS_COLUMN),
198            (schemata::SCHEMA_OPTS, OPTIONS_COLUMN),
199        ]
200    } else {
201        vec![(schemata::SCHEMA_NAME, SCHEMAS_COLUMN)]
202    };
203
204    let filters = vec![col(schemata::CATALOG_NAME).eq(lit(query_ctx.current_catalog()))];
205    let like_field = Some(schemata::SCHEMA_NAME);
206    let sort = vec![col(schemata::SCHEMA_NAME).sort(true, true)];
207
208    query_from_information_schema_dataframe(
209        query_engine,
210        catalog_manager,
211        query_ctx,
212        SCHEMATA,
213        vec![],
214        projects,
215        filters,
216        like_field,
217        sort,
218        &stmt.kind,
219    )
220    .await
221}
222
223/// Replaces column identifier references in a SQL expression.
224/// Used for backward compatibility where old column names should work with new ones.
225fn replace_column_in_expr(expr: &mut sqlparser::ast::Expr, from_column: &str, to_column: &str) {
226    let _ = visit_expressions_mut(expr, |e| {
227        match e {
228            sqlparser::ast::Expr::Identifier(ident)
229                if ident.value.eq_ignore_ascii_case(from_column) =>
230            {
231                ident.value = to_column.to_string();
232            }
233            sqlparser::ast::Expr::CompoundIdentifier(idents) => {
234                if let Some(last) = idents.last_mut()
235                    && last.value.eq_ignore_ascii_case(from_column)
236                {
237                    last.value = to_column.to_string();
238                }
239            }
240            _ => {}
241        }
242        ControlFlow::<()>::Continue(())
243    });
244}
245
246/// Cast a `show` statement execution into a query from tables in  `information_schema`.
247/// - `table_name`: the table name in `information_schema`,
248/// - `projects`: query projection, a list of `(column, renamed_column)`,
249/// - `filters`: filter expressions for query,
250/// - `like_field`: the field to filter by the predicate `ShowKind::Like`,
251/// - `sort`: sort the results by the specified sorting expressions,
252/// - `kind`: the show kind
253#[allow(clippy::too_many_arguments)]
254async fn query_from_information_schema_table(
255    query_engine: &QueryEngineRef,
256    catalog_manager: &CatalogManagerRef,
257    query_ctx: QueryContextRef,
258    table_name: &str,
259    select: Vec<Expr>,
260    projects: Vec<(&str, &str)>,
261    filters: Vec<Expr>,
262    like_field: Option<&str>,
263    sort: Vec<SortExpr>,
264    kind: ShowKind,
265) -> Result<Output> {
266    let dataframe = query_from_information_schema_dataframe(
267        query_engine,
268        catalog_manager,
269        query_ctx,
270        table_name,
271        select,
272        projects,
273        filters,
274        like_field,
275        sort,
276        &kind,
277    )
278    .await?;
279    dataframe_to_output(dataframe).await
280}
281
282async fn dataframe_to_output(dataframe: DataFrame) -> Result<Output> {
283    let stream = dataframe.execute_stream().await?;
284    Ok(Output::new_with_stream(Box::pin(
285        RecordBatchStreamAdapter::try_new(stream).context(error::CreateRecordBatchSnafu)?,
286    )))
287}
288
289/// Builds the [`DataFrame`] for a `SHOW` statement without executing it,
290/// so `Describe` handlers can derive the output schema from the same
291/// projection the executor uses.
292#[allow(clippy::too_many_arguments)]
293async fn query_from_information_schema_dataframe(
294    query_engine: &QueryEngineRef,
295    catalog_manager: &CatalogManagerRef,
296    query_ctx: QueryContextRef,
297    table_name: &str,
298    select: Vec<Expr>,
299    projects: Vec<(&str, &str)>,
300    filters: Vec<Expr>,
301    like_field: Option<&str>,
302    sort: Vec<SortExpr>,
303    kind: &ShowKind,
304) -> Result<DataFrame> {
305    let table = catalog_manager
306        .table(
307            query_ctx.current_catalog(),
308            INFORMATION_SCHEMA_NAME,
309            table_name,
310            Some(&query_ctx),
311        )
312        .await
313        .context(error::CatalogSnafu)?
314        .with_context(|| error::TableNotFoundSnafu {
315            table: format_full_table_name(
316                query_ctx.current_catalog(),
317                INFORMATION_SCHEMA_NAME,
318                table_name,
319            ),
320        })?;
321
322    let dataframe = query_engine.read_table(table)?;
323
324    // Apply filters
325    let dataframe = filters.into_iter().try_fold(dataframe, |df, expr| {
326        df.filter(expr).context(error::PlanSqlSnafu)
327    })?;
328
329    // Apply `like` predicate if exists
330    let dataframe = if let (ShowKind::Like(ident), Some(field)) = (&kind, like_field) {
331        dataframe
332            .filter(col(field).like(lit(ident.value.clone())))
333            .context(error::PlanSqlSnafu)?
334    } else {
335        dataframe
336    };
337
338    // Apply sorting
339    let dataframe = if sort.is_empty() {
340        dataframe
341    } else {
342        dataframe.sort(sort).context(error::PlanSqlSnafu)?
343    };
344
345    // Apply select
346    let dataframe = if select.is_empty() {
347        if projects.is_empty() {
348            dataframe
349        } else {
350            let projection = projects
351                .iter()
352                .map(|x| col(x.0).alias(x.1))
353                .collect::<Vec<_>>();
354            dataframe.select(projection).context(error::PlanSqlSnafu)?
355        }
356    } else {
357        dataframe.select(select).context(error::PlanSqlSnafu)?
358    };
359
360    // Apply projection
361    let dataframe = projects
362        .into_iter()
363        .try_fold(dataframe, |df, (column, renamed_column)| {
364            df.with_column_renamed(column, renamed_column)
365                .context(error::PlanSqlSnafu)
366        })?;
367
368    let dataframe = match kind {
369        ShowKind::All | ShowKind::Like(_) => {
370            // Like kind is processed above
371            dataframe
372        }
373        ShowKind::Where(filter) => {
374            // Cast the results into VIEW for `where` clause,
375            // which is evaluated against the column names displayed by the SHOW statement.
376            let view = dataframe.into_view();
377            let dataframe = SessionContext::new_with_state(
378                query_engine
379                    .engine_context(query_ctx.clone())?
380                    .state()
381                    .clone(),
382            )
383            .read_table(view)?;
384
385            let planner = query_engine.planner();
386            let planner = planner
387                .as_any()
388                .downcast_ref::<DfLogicalPlanner>()
389                .expect("Must be the datafusion planner");
390
391            let filter = planner
392                .sql_to_expr(filter.clone(), dataframe.schema(), false, query_ctx)
393                .await?;
394
395            // Apply the `where` clause filters
396            dataframe.filter(filter).context(error::PlanSqlSnafu)?
397        }
398    };
399
400    Ok(dataframe)
401}
402
403/// Execute `SHOW COLUMNS` statement.
404pub async fn show_columns(
405    stmt: ShowColumns,
406    query_engine: &QueryEngineRef,
407    catalog_manager: &CatalogManagerRef,
408    query_ctx: QueryContextRef,
409) -> Result<Output> {
410    let dataframe = show_columns_dataframe(&stmt, query_engine, catalog_manager, query_ctx).await?;
411    dataframe_to_output(dataframe).await
412}
413
414/// Builds the [`DataFrame`] for `SHOW COLUMNS` without executing it.
415pub async fn show_columns_dataframe(
416    stmt: &ShowColumns,
417    query_engine: &QueryEngineRef,
418    catalog_manager: &CatalogManagerRef,
419    query_ctx: QueryContextRef,
420) -> Result<DataFrame> {
421    let schema_name = if let Some(database) = &stmt.database {
422        database.clone()
423    } else {
424        query_ctx.current_schema()
425    };
426
427    let projects = if stmt.full {
428        vec![
429            (columns::COLUMN_NAME, FIELD_COLUMN),
430            (columns::DATA_TYPE, COLUMN_TYPE_COLUMN),
431            (columns::COLLATION_NAME, COLUMN_COLLATION_COLUMN),
432            (columns::IS_NULLABLE, COLUMN_NULLABLE_COLUMN),
433            (columns::COLUMN_KEY, COLUMN_KEY_COLUMN),
434            (columns::COLUMN_DEFAULT, COLUMN_DEFAULT_COLUMN),
435            (columns::COLUMN_COMMENT, COLUMN_COMMENT_COLUMN),
436            (columns::PRIVILEGES, COLUMN_PRIVILEGES_COLUMN),
437            (columns::EXTRA, COLUMN_EXTRA_COLUMN),
438            (columns::GREPTIME_DATA_TYPE, COLUMN_GREPTIME_TYPE_COLUMN),
439        ]
440    } else {
441        vec![
442            (columns::COLUMN_NAME, FIELD_COLUMN),
443            (columns::DATA_TYPE, COLUMN_TYPE_COLUMN),
444            (columns::IS_NULLABLE, COLUMN_NULLABLE_COLUMN),
445            (columns::COLUMN_KEY, COLUMN_KEY_COLUMN),
446            (columns::COLUMN_DEFAULT, COLUMN_DEFAULT_COLUMN),
447            (columns::EXTRA, COLUMN_EXTRA_COLUMN),
448            (columns::GREPTIME_DATA_TYPE, COLUMN_GREPTIME_TYPE_COLUMN),
449        ]
450    };
451
452    let filters = vec![
453        col(columns::TABLE_NAME).eq(lit(&stmt.table)),
454        col(columns::TABLE_SCHEMA).eq(lit(schema_name.clone())),
455        col(columns::TABLE_CATALOG).eq(lit(query_ctx.current_catalog())),
456    ];
457    let like_field = Some(columns::COLUMN_NAME);
458    let sort = vec![col(columns::COLUMN_NAME).sort(true, true)];
459
460    query_from_information_schema_dataframe(
461        query_engine,
462        catalog_manager,
463        query_ctx,
464        COLUMNS,
465        vec![],
466        projects,
467        filters,
468        like_field,
469        sort,
470        &stmt.kind,
471    )
472    .await
473}
474
475/// Execute `SHOW INDEX` statement.
476pub async fn show_index(
477    stmt: ShowIndex,
478    query_engine: &QueryEngineRef,
479    catalog_manager: &CatalogManagerRef,
480    query_ctx: QueryContextRef,
481) -> Result<Output> {
482    let dataframe = show_index_dataframe(&stmt, query_engine, catalog_manager, query_ctx).await?;
483    dataframe_to_output(dataframe).await
484}
485
486/// Builds the [`DataFrame`] for `SHOW INDEX` without executing it.
487pub async fn show_index_dataframe(
488    stmt: &ShowIndex,
489    query_engine: &QueryEngineRef,
490    catalog_manager: &CatalogManagerRef,
491    query_ctx: QueryContextRef,
492) -> Result<DataFrame> {
493    let schema_name = if let Some(database) = &stmt.database {
494        database.clone()
495    } else {
496        query_ctx.current_schema()
497    };
498
499    let select = vec![
500        col(statistics::TABLE_NAME).alias(INDEX_TABLE_COLUMN),
501        col(statistics::NON_UNIQUE).alias(INDEX_NONT_UNIQUE_COLUMN),
502        col(statistics::INDEX_NAME).alias(INDEX_KEY_NAME_COLUMN),
503        col(statistics::SEQ_IN_INDEX).alias(INDEX_SEQ_IN_INDEX_COLUMN),
504        col(statistics::COLUMN_NAME).alias(INDEX_COLUMN_NAME_COLUMN),
505        col(statistics::COLLATION).alias(COLUMN_COLLATION_COLUMN),
506        col(statistics::CARDINALITY).alias(INDEX_CARDINALITY_COLUMN),
507        col(statistics::SUB_PART).alias(INDEX_SUB_PART_COLUMN),
508        col(statistics::PACKED).alias(INDEX_PACKED_COLUMN),
509        col(statistics::NULLABLE).alias(COLUMN_NULLABLE_COLUMN),
510        col(statistics::INDEX_TYPE).alias(INDEX_INDEX_TYPE_COLUMN),
511        col(statistics::COMMENT).alias(COLUMN_COMMENT_COLUMN),
512        col(statistics::INDEX_COMMENT).alias(INDEX_COMMENT_COLUMN),
513        col(statistics::IS_VISIBLE).alias(INDEX_VISIBLE_COLUMN),
514        col(statistics::EXPRESSION).alias(INDEX_EXPRESSION_COLUMN),
515    ];
516
517    let projects = vec![
518        (statistics::TABLE_NAME, INDEX_TABLE_COLUMN),
519        (INDEX_NONT_UNIQUE_COLUMN, INDEX_NONT_UNIQUE_COLUMN),
520        (statistics::INDEX_NAME, INDEX_KEY_NAME_COLUMN),
521        (statistics::SEQ_IN_INDEX, INDEX_SEQ_IN_INDEX_COLUMN),
522        (statistics::COLUMN_NAME, INDEX_COLUMN_NAME_COLUMN),
523        (COLUMN_COLLATION_COLUMN, COLUMN_COLLATION_COLUMN),
524        (INDEX_CARDINALITY_COLUMN, INDEX_CARDINALITY_COLUMN),
525        (INDEX_SUB_PART_COLUMN, INDEX_SUB_PART_COLUMN),
526        (INDEX_PACKED_COLUMN, INDEX_PACKED_COLUMN),
527        (COLUMN_NULLABLE_COLUMN, COLUMN_NULLABLE_COLUMN),
528        (statistics::INDEX_TYPE, INDEX_INDEX_TYPE_COLUMN),
529        (COLUMN_COMMENT_COLUMN, COLUMN_COMMENT_COLUMN),
530        (INDEX_COMMENT_COLUMN, INDEX_COMMENT_COLUMN),
531        (INDEX_VISIBLE_COLUMN, INDEX_VISIBLE_COLUMN),
532        (INDEX_EXPRESSION_COLUMN, INDEX_EXPRESSION_COLUMN),
533    ];
534
535    let filters = vec![
536        col(statistics::TABLE_NAME).eq(lit(&stmt.table)),
537        col(statistics::TABLE_SCHEMA).eq(lit(schema_name.clone())),
538    ];
539    let like_field = None;
540    let sort = vec![
541        col(statistics::INDEX_NAME).sort(true, true),
542        col(statistics::SEQ_IN_INDEX).sort(true, true),
543    ];
544
545    query_from_information_schema_dataframe(
546        query_engine,
547        catalog_manager,
548        query_ctx,
549        STATISTICS,
550        select,
551        projects,
552        filters,
553        like_field,
554        sort,
555        &stmt.kind,
556    )
557    .await
558}
559
560/// Execute `SHOW REGION` statement.
561pub async fn show_region(
562    stmt: ShowRegion,
563    query_engine: &QueryEngineRef,
564    catalog_manager: &CatalogManagerRef,
565    query_ctx: QueryContextRef,
566) -> Result<Output> {
567    let dataframe = show_region_dataframe(&stmt, query_engine, catalog_manager, query_ctx).await?;
568    dataframe_to_output(dataframe).await
569}
570
571/// Builds the [`DataFrame`] for `SHOW REGION` without executing it.
572pub async fn show_region_dataframe(
573    stmt: &ShowRegion,
574    query_engine: &QueryEngineRef,
575    catalog_manager: &CatalogManagerRef,
576    query_ctx: QueryContextRef,
577) -> Result<DataFrame> {
578    let schema_name = if let Some(database) = &stmt.database {
579        database.clone()
580    } else {
581        query_ctx.current_schema()
582    };
583
584    let filters = vec![
585        col(region_peers::TABLE_NAME).eq(lit(&stmt.table)),
586        col(region_peers::TABLE_SCHEMA).eq(lit(schema_name.clone())),
587        col(region_peers::TABLE_CATALOG).eq(lit(query_ctx.current_catalog())),
588    ];
589    let projects = vec![
590        (region_peers::TABLE_NAME, "Table"),
591        (region_peers::REGION_ID, "Region"),
592        (region_peers::PEER_ID, "Peer"),
593        (region_peers::IS_LEADER, "Leader"),
594    ];
595
596    let like_field = None;
597    let sort = vec![
598        col(columns::REGION_ID).sort(true, true),
599        col(columns::PEER_ID).sort(true, true),
600    ];
601
602    query_from_information_schema_dataframe(
603        query_engine,
604        catalog_manager,
605        query_ctx,
606        REGION_PEERS,
607        vec![],
608        projects,
609        filters,
610        like_field,
611        sort,
612        &stmt.kind,
613    )
614    .await
615}
616
617/// Execute [`ShowTables`] statement and return the [`Output`] if success.
618pub async fn show_tables(
619    stmt: ShowTables,
620    query_engine: &QueryEngineRef,
621    catalog_manager: &CatalogManagerRef,
622    query_ctx: QueryContextRef,
623) -> Result<Output> {
624    let dataframe = show_tables_dataframe(&stmt, query_engine, catalog_manager, query_ctx).await?;
625    dataframe_to_output(dataframe).await
626}
627
628/// Builds the [`DataFrame`] for [`ShowTables`] without executing it.
629pub async fn show_tables_dataframe(
630    stmt: &ShowTables,
631    query_engine: &QueryEngineRef,
632    catalog_manager: &CatalogManagerRef,
633    query_ctx: QueryContextRef,
634) -> Result<DataFrame> {
635    let schema_name = if let Some(database) = &stmt.database {
636        database.clone()
637    } else {
638        query_ctx.current_schema()
639    };
640
641    // MySQL renames `table_name` to `Tables_in_{schema}` for protocol compatibility
642    let tables_column = format!("Tables_in_{}", schema_name);
643    let projects = if stmt.full {
644        vec![
645            (tables::TABLE_NAME, tables_column.as_str()),
646            (tables::TABLE_TYPE, TABLE_TYPE_COLUMN),
647        ]
648    } else {
649        vec![(tables::TABLE_NAME, tables_column.as_str())]
650    };
651    let filters = vec![
652        col(tables::TABLE_SCHEMA).eq(lit(schema_name.clone())),
653        col(tables::TABLE_CATALOG).eq(lit(query_ctx.current_catalog())),
654    ];
655    let like_field = Some(tables::TABLE_NAME);
656    let sort = vec![col(tables::TABLE_NAME).sort(true, true)];
657
658    // Transform the WHERE clause for backward compatibility:
659    // Replace "Tables" with "Tables_in_{schema}" to support old queries
660    let rewritten_kind = match &stmt.kind {
661        ShowKind::Where(filter) => {
662            let mut filter = filter.clone();
663            replace_column_in_expr(&mut filter, "Tables", &tables_column);
664            ShowKind::Where(filter)
665        }
666        kind => kind.clone(),
667    };
668
669    query_from_information_schema_dataframe(
670        query_engine,
671        catalog_manager,
672        query_ctx,
673        TABLES,
674        vec![],
675        projects,
676        filters,
677        like_field,
678        sort,
679        &rewritten_kind,
680    )
681    .await
682}
683
684/// Execute [`ShowTableStatus`] statement and return the [`Output`] if success.
685pub async fn show_table_status(
686    stmt: ShowTableStatus,
687    query_engine: &QueryEngineRef,
688    catalog_manager: &CatalogManagerRef,
689    query_ctx: QueryContextRef,
690) -> Result<Output> {
691    let dataframe =
692        show_table_status_dataframe(&stmt, query_engine, catalog_manager, query_ctx).await?;
693    dataframe_to_output(dataframe).await
694}
695
696/// Builds the [`DataFrame`] for [`ShowTableStatus`] without executing it.
697pub async fn show_table_status_dataframe(
698    stmt: &ShowTableStatus,
699    query_engine: &QueryEngineRef,
700    catalog_manager: &CatalogManagerRef,
701    query_ctx: QueryContextRef,
702) -> Result<DataFrame> {
703    let schema_name = if let Some(database) = &stmt.database {
704        database.clone()
705    } else {
706        query_ctx.current_schema()
707    };
708
709    // Refer to https://dev.mysql.com/doc/refman/8.4/en/show-table-status.html
710    let projects = vec![
711        (tables::TABLE_NAME, "Name"),
712        (tables::ENGINE, "Engine"),
713        (tables::VERSION, "Version"),
714        (tables::ROW_FORMAT, "Row_format"),
715        (tables::TABLE_ROWS, "Rows"),
716        (tables::AVG_ROW_LENGTH, "Avg_row_length"),
717        (tables::DATA_LENGTH, "Data_length"),
718        (tables::MAX_DATA_LENGTH, "Max_data_length"),
719        (tables::INDEX_LENGTH, "Index_length"),
720        (tables::DATA_FREE, "Data_free"),
721        (tables::AUTO_INCREMENT, "Auto_increment"),
722        (tables::CREATE_TIME, "Create_time"),
723        (tables::UPDATE_TIME, "Update_time"),
724        (tables::CHECK_TIME, "Check_time"),
725        (tables::TABLE_COLLATION, "Collation"),
726        (tables::CHECKSUM, "Checksum"),
727        (tables::CREATE_OPTIONS, "Create_options"),
728        (tables::TABLE_COMMENT, "Comment"),
729    ];
730
731    let filters = vec![
732        col(tables::TABLE_SCHEMA).eq(lit(schema_name.clone())),
733        col(tables::TABLE_CATALOG).eq(lit(query_ctx.current_catalog())),
734    ];
735    let like_field = Some(tables::TABLE_NAME);
736    let sort = vec![col(tables::TABLE_NAME).sort(true, true)];
737
738    query_from_information_schema_dataframe(
739        query_engine,
740        catalog_manager,
741        query_ctx,
742        TABLES,
743        vec![],
744        projects,
745        filters,
746        like_field,
747        sort,
748        &stmt.kind,
749    )
750    .await
751}
752
753/// Execute `SHOW COLLATION` statement and returns the `Output` if success.
754pub async fn show_collations(
755    kind: ShowKind,
756    query_engine: &QueryEngineRef,
757    catalog_manager: &CatalogManagerRef,
758    query_ctx: QueryContextRef,
759) -> Result<Output> {
760    let dataframe =
761        show_collations_dataframe(&kind, query_engine, catalog_manager, query_ctx).await?;
762    dataframe_to_output(dataframe).await
763}
764
765/// Builds the [`DataFrame`] for `SHOW COLLATION` without executing it.
766pub async fn show_collations_dataframe(
767    kind: &ShowKind,
768    query_engine: &QueryEngineRef,
769    catalog_manager: &CatalogManagerRef,
770    query_ctx: QueryContextRef,
771) -> Result<DataFrame> {
772    // Refer to https://dev.mysql.com/doc/refman/8.0/en/show-collation.html
773    let projects = vec![
774        ("collation_name", "Collation"),
775        ("character_set_name", "Charset"),
776        ("id", "Id"),
777        ("is_default", "Default"),
778        ("is_compiled", "Compiled"),
779        ("sortlen", "Sortlen"),
780    ];
781
782    let filters = vec![];
783    let like_field = Some("collation_name");
784    let sort = vec![];
785
786    query_from_information_schema_dataframe(
787        query_engine,
788        catalog_manager,
789        query_ctx,
790        COLLATIONS,
791        vec![],
792        projects,
793        filters,
794        like_field,
795        sort,
796        kind,
797    )
798    .await
799}
800
801/// Execute `SHOW CHARSET` statement and returns the `Output` if success.
802pub async fn show_charsets(
803    kind: ShowKind,
804    query_engine: &QueryEngineRef,
805    catalog_manager: &CatalogManagerRef,
806    query_ctx: QueryContextRef,
807) -> Result<Output> {
808    let dataframe =
809        show_charsets_dataframe(&kind, query_engine, catalog_manager, query_ctx).await?;
810    dataframe_to_output(dataframe).await
811}
812
813/// Builds the [`DataFrame`] for `SHOW CHARSET` without executing it.
814pub async fn show_charsets_dataframe(
815    kind: &ShowKind,
816    query_engine: &QueryEngineRef,
817    catalog_manager: &CatalogManagerRef,
818    query_ctx: QueryContextRef,
819) -> Result<DataFrame> {
820    // Refer to https://dev.mysql.com/doc/refman/8.0/en/show-character-set.html
821    let projects = vec![
822        ("character_set_name", "Charset"),
823        ("description", "Description"),
824        ("default_collate_name", "Default collation"),
825        ("maxlen", "Maxlen"),
826    ];
827
828    let filters = vec![];
829    let like_field = Some("character_set_name");
830    let sort = vec![];
831
832    query_from_information_schema_dataframe(
833        query_engine,
834        catalog_manager,
835        query_ctx,
836        CHARACTER_SETS,
837        vec![],
838        projects,
839        filters,
840        like_field,
841        sort,
842        kind,
843    )
844    .await
845}
846
847/// Shows one supported query optimizer option, returning `None` for legacy SHOW handling.
848pub fn show_query_option(
849    stmt: ShowVariables,
850    query_ctx: QueryContextRef,
851    engine: &dyn crate::QueryEngine,
852) -> Result<Option<Output>> {
853    let name = sql::ast::object_name_to_string_unquoted(&stmt.variable);
854    let Some(canonical) = session::query_options::canonical_query_option_name(&name) else {
855        if name.to_ascii_lowercase().starts_with("query.")
856            || name.to_ascii_lowercase().starts_with("datafusion.")
857        {
858            session::query_options::parse_query_option(
859                &name,
860                "",
861                session::query_options::MAX_PUBLIC_PARALLELISM,
862            )
863            .map_err(|error| {
864                error::InvalidQueryContextExtensionSnafu {
865                    reason: error.to_string(),
866                }
867                .build()
868            })?;
869        }
870        return Ok(None);
871    };
872    let context = engine.engine_context(query_ctx.clone())?;
873    let value = match canonical.as_str() {
874        "query.allow_query_fallback" => context
875            .state()
876            .config_options()
877            .extensions
878            .get::<crate::dist_plan::DistPlannerOptions>()
879            .map(|options| options.allow_query_fallback)
880            .unwrap_or(false)
881            .to_string(),
882        "query.enable_remote_dynamic_filter_pushdown" => {
883            crate::options::remote_dyn_filter_pushdown_enabled_from_extensions(
884                &context.query_ctx().extensions(),
885            )?
886            .to_string()
887        }
888        "query.parallelism" => context.state().config().target_partitions().to_string(),
889        "datafusion.optimizer.filter_null_join_keys"
890            if context
891                .state()
892                .analyzer()
893                .rules
894                .iter()
895                .any(|rule| rule.name() == "DistPlannerAnalyzer")
896                && context
897                    .query_ctx()
898                    .extension("datafusion.optimizer.filter_null_join_keys")
899                    .is_none() =>
900        {
901            "true".to_string()
902        }
903        _ => context
904            .state()
905            .config_options()
906            .entries()
907            .into_iter()
908            .find(|entry| entry.key == canonical)
909            .and_then(|entry| entry.value)
910            .ok_or_else(|| {
911                error::InvalidQueryContextExtensionSnafu {
912                    reason: format!(
913                        "No DataFusion configuration entry for supported option {canonical}"
914                    ),
915                }
916                .build()
917            })?,
918    };
919    let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
920        name.clone(),
921        ConcreteDataType::string_datatype(),
922        false,
923    )]));
924    let records = RecordBatches::try_from_columns(
925        schema,
926        vec![Arc::new(StringVector::from(vec![value])) as _],
927    )
928    .context(error::CreateRecordBatchSnafu)?;
929    Ok(Some(Output::new_with_record_batches(records)))
930}
931
932pub fn show_variable(stmt: ShowVariables, query_ctx: QueryContextRef) -> Result<Output> {
933    let variable = stmt.variable.to_string().to_uppercase();
934    let value = match variable.as_str() {
935        "SYSTEM_TIME_ZONE" | "SYSTEM_TIMEZONE" => get_timezone(None).to_string(),
936        "TIME_ZONE" | "TIMEZONE" => query_ctx.timezone().to_string(),
937        "READ_PREFERENCE" => query_ctx.read_preference().to_string(),
938        "DATESTYLE" => {
939            let (style, order) = *query_ctx.configuration_parameter().pg_datetime_style();
940            format!("{}, {}", style, order)
941        }
942        "INTERVALSTYLE" => {
943            let style = *query_ctx
944                .configuration_parameter()
945                .pg_intervalstyle_format();
946            style.to_string()
947        }
948        "MAX_EXECUTION_TIME"
949            if query_ctx.channel() == Channel::Mysql => {
950                query_ctx.query_timeout_as_millis().to_string()
951            }
952        "STATEMENT_TIMEOUT"
953            // Add time units to postgres query timeout display.
954            if query_ctx.channel() == Channel::Postgres => {
955                let mut timeout = query_ctx.query_timeout_as_millis().to_string();
956                timeout.push_str("ms");
957                timeout
958            }
959        _ => return UnsupportedVariableSnafu { name: variable }.fail(),
960    };
961    let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
962        variable,
963        ConcreteDataType::string_datatype(),
964        false,
965    )]));
966    let records = RecordBatches::try_from_columns(
967        schema,
968        vec![Arc::new(StringVector::from(vec![value])) as _],
969    )
970    .context(error::CreateRecordBatchSnafu)?;
971    Ok(Output::new_with_record_batches(records))
972}
973
974pub async fn show_status(_query_ctx: QueryContextRef) -> Result<Output> {
975    let schema = Arc::new(Schema::new(vec![
976        ColumnSchema::new("Variable_name", ConcreteDataType::string_datatype(), false),
977        ColumnSchema::new("Value", ConcreteDataType::string_datatype(), true),
978    ]));
979    let records = RecordBatches::try_from_columns(
980        schema,
981        vec![
982            Arc::new(StringVector::from(Vec::<&str>::new())) as _,
983            Arc::new(StringVector::from(Vec::<&str>::new())) as _,
984        ],
985    )
986    .context(error::CreateRecordBatchSnafu)?;
987    Ok(Output::new_with_record_batches(records))
988}
989
990pub async fn show_search_path(_query_ctx: QueryContextRef) -> Result<Output> {
991    let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
992        "search_path",
993        ConcreteDataType::string_datatype(),
994        false,
995    )]));
996    let records = RecordBatches::try_from_columns(
997        schema,
998        vec![Arc::new(StringVector::from(vec![_query_ctx.current_schema()])) as _],
999    )
1000    .context(error::CreateRecordBatchSnafu)?;
1001    Ok(Output::new_with_record_batches(records))
1002}
1003
1004pub fn show_create_database(database_name: &str, options: OptionMap) -> Result<Output> {
1005    let stmt = CreateDatabase {
1006        name: ObjectName::from(vec![Ident::new(database_name)]),
1007        if_not_exists: true,
1008        options,
1009    };
1010    let sql = format!("{stmt}");
1011    let columns = vec![
1012        Arc::new(StringVector::from(vec![database_name.to_string()])) as _,
1013        Arc::new(StringVector::from(vec![sql])) as _,
1014    ];
1015    let records =
1016        RecordBatches::try_from_columns(SHOW_CREATE_DATABASE_OUTPUT_SCHEMA.clone(), columns)
1017            .context(error::CreateRecordBatchSnafu)?;
1018    Ok(Output::new_with_record_batches(records))
1019}
1020
1021pub fn show_create_table(
1022    table_info: TableInfoRef,
1023    schema_options: Option<SchemaOptions>,
1024    partitions: Option<Partitions>,
1025    query_ctx: QueryContextRef,
1026) -> Result<Output> {
1027    let table_name = table_info.name.clone();
1028
1029    let quote_style = query_ctx.quote_style();
1030
1031    let mut stmt = create_table_stmt(&table_info, schema_options, quote_style)?;
1032    stmt.partitions = partitions.map(|mut p| {
1033        p.set_quote(quote_style);
1034        p
1035    });
1036    let sql = format!("{}", stmt);
1037    let columns = vec![
1038        Arc::new(StringVector::from(vec![table_name])) as _,
1039        Arc::new(StringVector::from(vec![sql])) as _,
1040    ];
1041    let records = RecordBatches::try_from_columns(SHOW_CREATE_TABLE_OUTPUT_SCHEMA.clone(), columns)
1042        .context(error::CreateRecordBatchSnafu)?;
1043
1044    Ok(Output::new_with_record_batches(records))
1045}
1046
1047pub fn show_create_foreign_table_for_pg(
1048    table: TableRef,
1049    _query_ctx: QueryContextRef,
1050) -> Result<Output> {
1051    let table_info = table.table_info();
1052
1053    let table_meta = &table_info.meta;
1054    let table_name = &table_info.name;
1055    let schema = &table_info.meta.schema;
1056    let is_metric_engine = is_metric_engine(&table_meta.engine);
1057
1058    let columns = schema
1059        .column_schemas()
1060        .iter()
1061        .filter_map(|c| {
1062            if is_metric_engine && is_metric_engine_internal_column(&c.name) {
1063                None
1064            } else {
1065                Some(format!(
1066                    "\"{}\" {}",
1067                    c.name,
1068                    c.data_type.postgres_datatype_name()
1069                ))
1070            }
1071        })
1072        .join(",\n  ");
1073
1074    let sql = format!(
1075        r#"CREATE FOREIGN TABLE ft_{} (
1076  {}
1077)
1078SERVER greptimedb
1079OPTIONS (table_name '{}')"#,
1080        table_name, columns, table_name
1081    );
1082
1083    let columns = vec![
1084        Arc::new(StringVector::from(vec![table_name.clone()])) as _,
1085        Arc::new(StringVector::from(vec![sql])) as _,
1086    ];
1087    let records = RecordBatches::try_from_columns(SHOW_CREATE_TABLE_OUTPUT_SCHEMA.clone(), columns)
1088        .context(error::CreateRecordBatchSnafu)?;
1089
1090    Ok(Output::new_with_record_batches(records))
1091}
1092
1093pub fn show_create_view(
1094    view_name: ObjectName,
1095    definition: &str,
1096    query_ctx: QueryContextRef,
1097) -> Result<Output> {
1098    let mut parser_ctx =
1099        ParserContext::new(query_ctx.sql_dialect(), definition).context(error::SqlSnafu)?;
1100
1101    let Statement::CreateView(create_view) =
1102        parser_ctx.parse_statement().context(error::SqlSnafu)?
1103    else {
1104        // MUST be `CreateView` statement.
1105        unreachable!();
1106    };
1107
1108    let stmt = CreateView {
1109        name: view_name.clone(),
1110        columns: create_view.columns,
1111        query: create_view.query,
1112        or_replace: create_view.or_replace,
1113        if_not_exists: create_view.if_not_exists,
1114    };
1115
1116    let sql = format!("{}", stmt);
1117    let columns = vec![
1118        Arc::new(StringVector::from(vec![view_name.to_string()])) as _,
1119        Arc::new(StringVector::from(vec![sql])) as _,
1120    ];
1121    let records = RecordBatches::try_from_columns(SHOW_CREATE_VIEW_OUTPUT_SCHEMA.clone(), columns)
1122        .context(error::CreateRecordBatchSnafu)?;
1123
1124    Ok(Output::new_with_record_batches(records))
1125}
1126
1127/// Execute [`ShowViews`] statement and return the [`Output`] if success.
1128pub async fn show_views(
1129    stmt: ShowViews,
1130    query_engine: &QueryEngineRef,
1131    catalog_manager: &CatalogManagerRef,
1132    query_ctx: QueryContextRef,
1133) -> Result<Output> {
1134    let dataframe = show_views_dataframe(&stmt, query_engine, catalog_manager, query_ctx).await?;
1135    dataframe_to_output(dataframe).await
1136}
1137
1138/// Builds the [`DataFrame`] for [`ShowViews`] without executing it.
1139pub async fn show_views_dataframe(
1140    stmt: &ShowViews,
1141    query_engine: &QueryEngineRef,
1142    catalog_manager: &CatalogManagerRef,
1143    query_ctx: QueryContextRef,
1144) -> Result<DataFrame> {
1145    let schema_name = if let Some(database) = &stmt.database {
1146        database.clone()
1147    } else {
1148        query_ctx.current_schema()
1149    };
1150
1151    let projects = vec![(tables::TABLE_NAME, VIEWS_COLUMN)];
1152    let filters = vec![
1153        col(tables::TABLE_SCHEMA).eq(lit(schema_name.clone())),
1154        col(tables::TABLE_CATALOG).eq(lit(query_ctx.current_catalog())),
1155    ];
1156    let like_field = Some(tables::TABLE_NAME);
1157    let sort = vec![col(tables::TABLE_NAME).sort(true, true)];
1158
1159    query_from_information_schema_dataframe(
1160        query_engine,
1161        catalog_manager,
1162        query_ctx,
1163        VIEWS,
1164        vec![],
1165        projects,
1166        filters,
1167        like_field,
1168        sort,
1169        &stmt.kind,
1170    )
1171    .await
1172}
1173
1174/// Execute [`ShowFlows`] statement and return the [`Output`] if success.
1175pub async fn show_flows(
1176    stmt: ShowFlows,
1177    query_engine: &QueryEngineRef,
1178    catalog_manager: &CatalogManagerRef,
1179    query_ctx: QueryContextRef,
1180) -> Result<Output> {
1181    let dataframe = show_flows_dataframe(&stmt, query_engine, catalog_manager, query_ctx).await?;
1182    dataframe_to_output(dataframe).await
1183}
1184
1185/// Builds the [`DataFrame`] for [`ShowFlows`] without executing it.
1186pub async fn show_flows_dataframe(
1187    stmt: &ShowFlows,
1188    query_engine: &QueryEngineRef,
1189    catalog_manager: &CatalogManagerRef,
1190    query_ctx: QueryContextRef,
1191) -> Result<DataFrame> {
1192    let projects = vec![(flows::FLOW_NAME, FLOWS_COLUMN)];
1193    let filters = vec![col(flows::TABLE_CATALOG).eq(lit(query_ctx.current_catalog()))];
1194    let like_field = Some(flows::FLOW_NAME);
1195    let sort = vec![col(flows::FLOW_NAME).sort(true, true)];
1196
1197    query_from_information_schema_dataframe(
1198        query_engine,
1199        catalog_manager,
1200        query_ctx,
1201        FLOWS,
1202        vec![],
1203        projects,
1204        filters,
1205        like_field,
1206        sort,
1207        &stmt.kind,
1208    )
1209    .await
1210}
1211
1212/// Execute [`ShowFlowStatus`] statement and return the [`Output`] if success.
1213pub async fn show_flow_status(
1214    stmt: ShowFlowStatus,
1215    query_engine: &QueryEngineRef,
1216    catalog_manager: &CatalogManagerRef,
1217    query_ctx: QueryContextRef,
1218) -> Result<Output> {
1219    let projects = vec![
1220        (flow_statistics::FLOW_ID, flow_statistics::FLOW_ID),
1221        (flow_statistics::FLOW_NAME, flow_statistics::FLOW_NAME),
1222        (flow_statistics::START_TIME, flow_statistics::START_TIME),
1223        (
1224            flow_statistics::LAST_EXECUTION_TIME,
1225            flow_statistics::LAST_EXECUTION_TIME,
1226        ),
1227        (
1228            flow_statistics::UPTIME_SECONDS,
1229            flow_statistics::UPTIME_SECONDS,
1230        ),
1231        (flow_statistics::STATE_SIZE, flow_statistics::STATE_SIZE),
1232    ];
1233    let like_field = Some(flow_statistics::FLOW_NAME);
1234    let sort = vec![col(flow_statistics::FLOW_NAME).sort(true, true)];
1235
1236    query_from_information_schema_table(
1237        query_engine,
1238        catalog_manager,
1239        query_ctx,
1240        FLOW_STATISTICS,
1241        vec![],
1242        projects,
1243        vec![],
1244        like_field,
1245        sort,
1246        stmt.kind,
1247    )
1248    .await
1249}
1250
1251#[cfg(feature = "enterprise")]
1252pub async fn show_triggers(
1253    stmt: sql::statements::show::trigger::ShowTriggers,
1254    query_engine: &QueryEngineRef,
1255    catalog_manager: &CatalogManagerRef,
1256    query_ctx: QueryContextRef,
1257) -> Result<Output> {
1258    const TRIGGER_NAME: &str = "trigger_name";
1259    const TRIGGERS_COLUMN: &str = "Triggers";
1260
1261    let projects = vec![(TRIGGER_NAME, TRIGGERS_COLUMN)];
1262    let like_field = Some(TRIGGER_NAME);
1263    let sort = vec![col(TRIGGER_NAME).sort(true, true)];
1264
1265    query_from_information_schema_table(
1266        query_engine,
1267        catalog_manager,
1268        query_ctx,
1269        catalog::information_schema::TRIGGERS,
1270        vec![],
1271        projects,
1272        vec![],
1273        like_field,
1274        sort,
1275        stmt.kind,
1276    )
1277    .await
1278}
1279
1280pub fn show_create_flow(
1281    flow_name: ObjectName,
1282    flow_val: FlowInfoValue,
1283    query_ctx: QueryContextRef,
1284) -> Result<Output> {
1285    let mut parser_ctx =
1286        ParserContext::new(query_ctx.sql_dialect(), flow_val.raw_sql()).context(error::SqlSnafu)?;
1287
1288    let query = parser_ctx.parse_statement().context(error::SqlSnafu)?;
1289
1290    // since prom ql will parse `now()` to a fixed time, we need to not use it for generating raw query
1291    let raw_query = match &query {
1292        Statement::Tql(_) => flow_val.raw_sql().clone(),
1293        _ => query.to_string(),
1294    };
1295
1296    let query = Box::new(SqlOrTql::try_from_statement(query, &raw_query).context(error::SqlSnafu)?);
1297
1298    let comment = if flow_val.comment().is_empty() {
1299        None
1300    } else {
1301        Some(flow_val.comment().clone())
1302    };
1303
1304    let stmt = CreateFlow {
1305        flow_name,
1306        sink_table_name: ObjectName::from(vec![
1307            Ident::new(&flow_val.sink_table_name().schema_name),
1308            Ident::new(&flow_val.sink_table_name().table_name),
1309        ]),
1310        // notice we don't want `OR REPLACE` and `IF NOT EXISTS` in same sql since it's unclear what to do
1311        // so we set `or_replace` to false.
1312        or_replace: false,
1313        if_not_exists: true,
1314        expire_after: flow_val.expire_after(),
1315        eval_interval: flow_val.eval_interval(),
1316        eval_offset: effective_eval_schedule_from_flow_info(&flow_val)
1317            .map_err(BoxedError::new)
1318            .context(error::QueryExecutionSnafu)?
1319            .map(|schedule| schedule.anchor_secs)
1320            .filter(|anchor_secs| *anchor_secs != 0),
1321        comment,
1322        flow_options: OptionMap::from_filtered_string_map(
1323            flow_val.options(),
1324            &[FlowType::FLOW_TYPE_KEY],
1325        ),
1326        query,
1327    };
1328
1329    let sql = format!("{}", stmt);
1330    let columns = vec![
1331        Arc::new(StringVector::from(vec![flow_val.flow_name().clone()])) as _,
1332        Arc::new(StringVector::from(vec![sql])) as _,
1333    ];
1334    let records = RecordBatches::try_from_columns(SHOW_CREATE_FLOW_OUTPUT_SCHEMA.clone(), columns)
1335        .context(error::CreateRecordBatchSnafu)?;
1336
1337    Ok(Output::new_with_record_batches(records))
1338}
1339
1340pub fn describe_table(table: TableRef) -> Result<Output> {
1341    let table_info = table.table_info();
1342    let columns_schemas = table_info.meta.schema.column_schemas();
1343    let columns = vec![
1344        describe_column_names(columns_schemas),
1345        describe_column_types(columns_schemas),
1346        describe_column_keys(columns_schemas, &table_info.meta.primary_key_indices),
1347        describe_column_nullables(columns_schemas),
1348        describe_column_defaults(columns_schemas),
1349        describe_column_semantic_types(columns_schemas, &table_info.meta.primary_key_indices),
1350    ];
1351    let records = RecordBatches::try_from_columns(DESCRIBE_TABLE_OUTPUT_SCHEMA.clone(), columns)
1352        .context(error::CreateRecordBatchSnafu)?;
1353    Ok(Output::new_with_record_batches(records))
1354}
1355
1356fn describe_column_names(columns_schemas: &[ColumnSchema]) -> VectorRef {
1357    Arc::new(StringVector::from_iterator(
1358        columns_schemas.iter().map(|cs| cs.name.as_str()),
1359    ))
1360}
1361
1362fn describe_column_types(columns_schemas: &[ColumnSchema]) -> VectorRef {
1363    Arc::new(StringVector::from(
1364        columns_schemas
1365            .iter()
1366            .map(|cs| describe_column_type_name(&cs.data_type))
1367            .collect::<Vec<_>>(),
1368    ))
1369}
1370
1371fn describe_column_type_name(data_type: &ConcreteDataType) -> String {
1372    match data_type {
1373        ConcreteDataType::Json(json_type) if json_type.is_json2() => "Json2".to_string(),
1374        data_type => data_type.name(),
1375    }
1376}
1377
1378fn describe_column_keys(
1379    columns_schemas: &[ColumnSchema],
1380    primary_key_indices: &[usize],
1381) -> VectorRef {
1382    Arc::new(StringVector::from_iterator(
1383        columns_schemas.iter().enumerate().map(|(i, cs)| {
1384            if cs.is_time_index() || primary_key_indices.contains(&i) {
1385                PRI_KEY
1386            } else {
1387                ""
1388            }
1389        }),
1390    ))
1391}
1392
1393fn describe_column_nullables(columns_schemas: &[ColumnSchema]) -> VectorRef {
1394    Arc::new(StringVector::from_iterator(columns_schemas.iter().map(
1395        |cs| {
1396            if cs.is_nullable() { YES_STR } else { NO_STR }
1397        },
1398    )))
1399}
1400
1401fn describe_column_defaults(columns_schemas: &[ColumnSchema]) -> VectorRef {
1402    Arc::new(StringVector::from(
1403        columns_schemas
1404            .iter()
1405            .map(|cs| {
1406                cs.default_constraint()
1407                    .map_or(String::from(""), |dc| dc.to_string())
1408            })
1409            .collect::<Vec<String>>(),
1410    ))
1411}
1412
1413fn describe_column_semantic_types(
1414    columns_schemas: &[ColumnSchema],
1415    primary_key_indices: &[usize],
1416) -> VectorRef {
1417    Arc::new(StringVector::from_iterator(
1418        columns_schemas.iter().enumerate().map(|(i, cs)| {
1419            if primary_key_indices.contains(&i) {
1420                SEMANTIC_TYPE_PRIMARY_KEY
1421            } else if cs.is_time_index() {
1422                SEMANTIC_TYPE_TIME_INDEX
1423            } else {
1424                SEMANTIC_TYPE_FIELD
1425            }
1426        }),
1427    ))
1428}
1429
1430// lists files in the frontend to reduce unnecessary scan requests repeated in each datanode.
1431pub async fn prepare_file_table_files(
1432    options: &HashMap<String, String>,
1433    local_file_access: &LocalFileAccess,
1434) -> Result<(ObjectStore, Vec<String>)> {
1435    let url = options
1436        .get(FILE_TABLE_LOCATION_KEY)
1437        .context(error::MissingRequiredFieldSnafu {
1438            name: FILE_TABLE_LOCATION_KEY,
1439        })?;
1440
1441    let regex = options
1442        .get(FILE_TABLE_PATTERN_KEY)
1443        .map(|x| Regex::new(x))
1444        .transpose()
1445        .context(error::BuildRegexSnafu)?;
1446    let backend = build_backend_with_path(url, options, local_file_access)
1447        .await
1448        .context(error::BuildBackendSnafu)?;
1449    let source = if let Some(filename) = backend.object_path {
1450        Source::Filename(filename)
1451    } else {
1452        Source::Dir
1453    };
1454    let lister = Lister::new(backend.object_store.clone(), source, url.clone(), regex);
1455    // If we scan files in a directory every time the database restarts,
1456    // then it might lead to a potential undefined behavior:
1457    // If a user adds a file with an incompatible schema to that directory,
1458    // it will make the external table unavailable.
1459    let files = lister
1460        .list()
1461        .await
1462        .context(error::ListObjectsSnafu)?
1463        .into_iter()
1464        .filter_map(|entry| {
1465            if entry.path().ends_with('/') {
1466                None
1467            } else {
1468                Some(entry.path().to_string())
1469            }
1470        })
1471        .collect::<Vec<_>>();
1472    Ok((backend.object_store, files))
1473}
1474
1475pub async fn infer_file_table_schema(
1476    object_store: &ObjectStore,
1477    files: &[String],
1478    options: &HashMap<String, String>,
1479) -> Result<Schema> {
1480    let format = parse_file_table_format(options)?;
1481    let merged = infer_schemas(object_store, files, format.as_ref())
1482        .await
1483        .context(error::InferSchemaSnafu)?;
1484    Schema::try_from(merged).context(error::ConvertSchemaSnafu)
1485}
1486
1487// Converts the file column schemas to table column schemas.
1488// Returns the column schemas and the time index column name.
1489//
1490// More specifically, this function will do the following:
1491// 1. Add a default time index column if there is no time index column
1492//    in the file column schemas, or
1493// 2. If the file column schemas contain a column with name conflicts with
1494//    the default time index column, it will replace the column schema
1495//    with the default one.
1496pub fn file_column_schemas_to_table(
1497    file_column_schemas: &[ColumnSchema],
1498) -> (Vec<ColumnSchema>, String) {
1499    let mut column_schemas = file_column_schemas.to_owned();
1500    if let Some(time_index_column) = column_schemas.iter().find(|c| c.is_time_index()) {
1501        let time_index = time_index_column.name.clone();
1502        return (column_schemas, time_index);
1503    }
1504
1505    let timestamp_type = ConcreteDataType::timestamp_millisecond_datatype();
1506    let default_zero = Value::Timestamp(Timestamp::new_millisecond(0));
1507    let timestamp_column_schema = ColumnSchema::new(greptime_timestamp(), timestamp_type, false)
1508        .with_time_index(true)
1509        .with_default_constraint(Some(ColumnDefaultConstraint::Value(default_zero)))
1510        .unwrap();
1511
1512    if let Some(column_schema) = column_schemas
1513        .iter_mut()
1514        .find(|column_schema| column_schema.name == greptime_timestamp())
1515    {
1516        // Replace the column schema with the default one
1517        *column_schema = timestamp_column_schema;
1518    } else {
1519        column_schemas.push(timestamp_column_schema);
1520    }
1521
1522    (column_schemas, greptime_timestamp().to_string())
1523}
1524
1525/// This function checks if the column schemas from a file can be matched with
1526/// the column schemas of a table.
1527///
1528/// More specifically, for each column seen in the table schema,
1529/// - If the same column does exist in the file schema, it checks if the data
1530///   type of the file column can be casted into the form of the table column.
1531/// - If the same column does not exist in the file schema, it checks if the
1532///   table column is nullable or has a default constraint.
1533pub fn check_file_to_table_schema_compatibility(
1534    file_column_schemas: &[ColumnSchema],
1535    table_column_schemas: &[ColumnSchema],
1536) -> Result<()> {
1537    let file_schemas_map = file_column_schemas
1538        .iter()
1539        .map(|s| (s.name.clone(), s))
1540        .collect::<HashMap<_, _>>();
1541
1542    for table_column in table_column_schemas {
1543        if let Some(file_column) = file_schemas_map.get(&table_column.name) {
1544            // TODO(zhongzc): a temporary solution, we should use `can_cast_to` once it's ready.
1545            ensure!(
1546                file_column
1547                    .data_type
1548                    .can_arrow_type_cast_to(&table_column.data_type),
1549                error::ColumnSchemaIncompatibleSnafu {
1550                    column: table_column.name.clone(),
1551                    file_type: file_column.data_type.clone(),
1552                    table_type: table_column.data_type.clone(),
1553                }
1554            );
1555        } else {
1556            ensure!(
1557                table_column.is_nullable() || table_column.default_constraint().is_some(),
1558                error::ColumnSchemaNoDefaultSnafu {
1559                    column: table_column.name.clone(),
1560                }
1561            );
1562        }
1563    }
1564
1565    Ok(())
1566}
1567
1568fn parse_file_table_format(options: &HashMap<String, String>) -> Result<Box<dyn FileFormat>> {
1569    Ok(
1570        match Format::try_from(options).context(error::ParseFileFormatSnafu)? {
1571            Format::Csv(format) => Box::new(format),
1572            Format::Json(format) => Box::new(format),
1573            Format::Parquet(format) => Box::new(format),
1574            Format::Orc(format) => Box::new(format),
1575        },
1576    )
1577}
1578
1579pub async fn show_processlist(
1580    stmt: ShowProcessList,
1581    query_engine: &QueryEngineRef,
1582    catalog_manager: &CatalogManagerRef,
1583    query_ctx: QueryContextRef,
1584) -> Result<Output> {
1585    let dataframe =
1586        show_processlist_dataframe(&stmt, query_engine, catalog_manager, query_ctx).await?;
1587    dataframe_to_output(dataframe).await
1588}
1589
1590/// Builds the [`DataFrame`] for `SHOW PROCESSLIST` without executing it.
1591pub async fn show_processlist_dataframe(
1592    stmt: &ShowProcessList,
1593    query_engine: &QueryEngineRef,
1594    catalog_manager: &CatalogManagerRef,
1595    query_ctx: QueryContextRef,
1596) -> Result<DataFrame> {
1597    let projects = if stmt.full {
1598        vec![
1599            (process_list::ID, "Id"),
1600            (process_list::CATALOG, "Catalog"),
1601            (process_list::SCHEMAS, "Schema"),
1602            (process_list::CLIENT, "Client"),
1603            (process_list::FRONTEND, "Frontend"),
1604            (process_list::START_TIMESTAMP, "Start Time"),
1605            (process_list::ELAPSED_TIME, "Elapsed Time"),
1606            (process_list::QUERY, "Query"),
1607        ]
1608    } else {
1609        vec![
1610            (process_list::ID, "Id"),
1611            (process_list::CATALOG, "Catalog"),
1612            (process_list::QUERY, "Query"),
1613            (process_list::ELAPSED_TIME, "Elapsed Time"),
1614        ]
1615    };
1616
1617    let filters = if query_ctx.current_user().is_admin() {
1618        vec![]
1619    } else {
1620        vec![col(process_list::CATALOG).eq(lit(query_ctx.current_catalog()))]
1621    };
1622    let like_field = None;
1623    let sort = vec![col("id").sort(true, true)];
1624    query_from_information_schema_dataframe(
1625        query_engine,
1626        catalog_manager,
1627        query_ctx.clone(),
1628        "process_list",
1629        vec![],
1630        projects,
1631        filters,
1632        like_field,
1633        sort,
1634        &ShowKind::All,
1635    )
1636    .await
1637}
1638
1639#[cfg(test)]
1640mod test {
1641    use std::sync::Arc;
1642
1643    use common_query::{Output, OutputData};
1644    use common_recordbatch::{RecordBatch, RecordBatches};
1645    use common_time::Timezone;
1646    use common_time::timestamp::TimeUnit;
1647    use datatypes::prelude::ConcreteDataType;
1648    use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema, Schema, SchemaRef};
1649    use datatypes::types::json_type::JsonNativeType;
1650    use datatypes::vectors::{StringVector, TimestampMillisecondVector, UInt32Vector, VectorRef};
1651    use session::context::QueryContextBuilder;
1652    use snafu::ResultExt;
1653    use sql::ast::{Ident, ObjectName};
1654    use sql::statements::show::ShowVariables;
1655    use table::TableRef;
1656    use table::test_util::MemTable;
1657
1658    use super::{describe_column_type_name, show_variable};
1659    use crate::error;
1660    use crate::error::Result;
1661    use crate::sql::{
1662        DESCRIBE_TABLE_OUTPUT_SCHEMA, NO_STR, SEMANTIC_TYPE_FIELD, SEMANTIC_TYPE_TIME_INDEX,
1663        YES_STR, describe_table,
1664    };
1665
1666    #[test]
1667    fn test_describe_table_multiple_columns() -> Result<()> {
1668        let table_name = "test_table";
1669        let schema = vec![
1670            ColumnSchema::new("t1", ConcreteDataType::uint32_datatype(), true),
1671            ColumnSchema::new(
1672                "t2",
1673                ConcreteDataType::timestamp_datatype(TimeUnit::Millisecond),
1674                false,
1675            )
1676            .with_default_constraint(Some(ColumnDefaultConstraint::Function(String::from(
1677                "current_timestamp()",
1678            ))))
1679            .unwrap()
1680            .with_time_index(true),
1681        ];
1682        let data = vec![
1683            Arc::new(UInt32Vector::from_slice([0])) as _,
1684            Arc::new(TimestampMillisecondVector::from_slice([0])) as _,
1685        ];
1686        let expected_columns = vec![
1687            Arc::new(StringVector::from(vec!["t1", "t2"])) as _,
1688            Arc::new(StringVector::from(vec!["UInt32", "TimestampMillisecond"])) as _,
1689            Arc::new(StringVector::from(vec!["", "PRI"])) as _,
1690            Arc::new(StringVector::from(vec![YES_STR, NO_STR])) as _,
1691            Arc::new(StringVector::from(vec!["", "current_timestamp()"])) as _,
1692            Arc::new(StringVector::from(vec![
1693                SEMANTIC_TYPE_FIELD,
1694                SEMANTIC_TYPE_TIME_INDEX,
1695            ])) as _,
1696        ];
1697
1698        describe_table_test_by_schema(table_name, schema, data, expected_columns)
1699    }
1700
1701    #[test]
1702    fn test_describe_column_type_name_json2() {
1703        assert_eq!(
1704            describe_column_type_name(&ConcreteDataType::json2(JsonNativeType::Null)),
1705            "Json2"
1706        );
1707        assert_eq!(
1708            describe_column_type_name(&ConcreteDataType::uint32_datatype()),
1709            "UInt32"
1710        );
1711    }
1712
1713    fn describe_table_test_by_schema(
1714        table_name: &str,
1715        schema: Vec<ColumnSchema>,
1716        data: Vec<VectorRef>,
1717        expected_columns: Vec<VectorRef>,
1718    ) -> Result<()> {
1719        let table_schema = SchemaRef::new(Schema::new(schema));
1720        let table = prepare_describe_table(table_name, table_schema, data);
1721
1722        let expected =
1723            RecordBatches::try_from_columns(DESCRIBE_TABLE_OUTPUT_SCHEMA.clone(), expected_columns)
1724                .context(error::CreateRecordBatchSnafu)?;
1725
1726        if let OutputData::RecordBatches(res) = describe_table(table)?.data {
1727            assert_eq!(res.take(), expected.take());
1728        } else {
1729            panic!("describe table must return record batch");
1730        }
1731
1732        Ok(())
1733    }
1734
1735    fn prepare_describe_table(
1736        table_name: &str,
1737        table_schema: SchemaRef,
1738        data: Vec<VectorRef>,
1739    ) -> TableRef {
1740        let record_batch = RecordBatch::new(table_schema, data).unwrap();
1741        MemTable::table(table_name, record_batch)
1742    }
1743
1744    #[test]
1745    fn test_show_variable() {
1746        assert_eq!(
1747            exec_show_variable("SYSTEM_TIME_ZONE", "Asia/Shanghai").unwrap(),
1748            "UTC"
1749        );
1750        assert_eq!(
1751            exec_show_variable("SYSTEM_TIMEZONE", "Asia/Shanghai").unwrap(),
1752            "UTC"
1753        );
1754        assert_eq!(
1755            exec_show_variable("TIME_ZONE", "Asia/Shanghai").unwrap(),
1756            "Asia/Shanghai"
1757        );
1758        assert_eq!(
1759            exec_show_variable("TIMEZONE", "Asia/Shanghai").unwrap(),
1760            "Asia/Shanghai"
1761        );
1762        assert!(exec_show_variable("TIME ZONE", "Asia/Shanghai").is_err());
1763        assert!(exec_show_variable("SYSTEM TIME ZONE", "Asia/Shanghai").is_err());
1764    }
1765
1766    fn exec_show_variable(variable: &str, tz: &str) -> Result<String> {
1767        let stmt = ShowVariables {
1768            variable: ObjectName::from(vec![Ident::new(variable)]),
1769        };
1770        let ctx = Arc::new(
1771            QueryContextBuilder::default()
1772                .timezone(Timezone::from_tz_string(tz).unwrap())
1773                .build(),
1774        );
1775        match show_variable(stmt, ctx) {
1776            Ok(Output {
1777                data: OutputData::RecordBatches(record),
1778                ..
1779            }) => {
1780                let record = record.take().first().cloned().unwrap();
1781                Ok(record.iter_column_as_string(0).next().unwrap().unwrap())
1782            }
1783            Ok(_) => unreachable!(),
1784            Err(e) => Err(e),
1785        }
1786    }
1787}