Skip to main content

servers/postgres/
handler.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
15use std::fmt::Debug;
16use std::pin::Pin;
17use std::sync::Arc;
18
19use async_trait::async_trait;
20use common_query::{Output, OutputData};
21use common_recordbatch::RecordBatch;
22use common_recordbatch::error::Result as RecordBatchResult;
23use common_telemetry::{debug, info, tracing};
24use datafusion::sql::sqlparser::ast::{CopyOption, CopyTarget, Statement as SqlParserStatement};
25use datafusion_common::ParamValues;
26use datafusion_expr::LogicalPlan;
27use datafusion_pg_catalog::sql::PostgresCompatibilityParser;
28use datatypes::prelude::ConcreteDataType;
29use datatypes::schema::{Schema, SchemaRef};
30use futures::{Sink, SinkExt, Stream, StreamExt, future, stream};
31use operator::statement::admin_output_schema;
32use pgwire::api::portal::{Format, Portal};
33use pgwire::api::query::{ExtendedQueryHandler, SimpleQueryHandler};
34use pgwire::api::results::{
35    CopyCsvOptions, CopyEncoder, CopyResponse, CopyTextOptions, DataRowEncoder,
36    DescribePortalResponse, DescribeStatementResponse, FieldInfo, QueryResponse, Response, Tag,
37};
38use pgwire::api::stmt::{QueryParser, StoredStatement};
39use pgwire::api::{ClientInfo, ErrorHandler, Type};
40use pgwire::error::{ErrorInfo, PgWireError, PgWireResult};
41use pgwire::messages::PgWireBackendMessage;
42use pgwire::messages::copy::CopyData;
43use pgwire::messages::data::DataRow;
44use query::dist_analyze_output_schema;
45use query::planner::DfLogicalPlanner;
46use query::query_engine::DescribeResult;
47use query::sql::DESCRIBE_TABLE_OUTPUT_SCHEMA;
48use session::Session;
49use session::context::QueryContextRef;
50use snafu::ResultExt;
51use sql::dialect::PostgreSqlDialect;
52use sql::parser::{ParseOptions, ParserContext};
53use sql::statements::statement::Statement;
54
55use crate::SqlPlan;
56use crate::error::{DataFusionSnafu, InferParameterTypesSnafu, Result};
57use crate::postgres::types::*;
58use crate::postgres::utils::convert_err;
59use crate::postgres::{PostgresServerHandlerInner, fixtures};
60use crate::query_handler::sql::ServerSqlQueryHandlerRef;
61
62impl PostgresServerHandlerInner {
63    fn new_query_context(&self) -> QueryContextRef {
64        let mut ctx = self.session.new_query_context();
65        Arc::make_mut(&mut ctx).set_batching_enabled(self.batching_enabled);
66        ctx
67    }
68}
69
70#[async_trait]
71impl SimpleQueryHandler for PostgresServerHandlerInner {
72    #[tracing::instrument(skip_all, fields(protocol = "postgres"))]
73    async fn do_query<C>(&self, client: &mut C, query: &str) -> PgWireResult<Vec<Response>>
74    where
75        C: ClientInfo + Sink<PgWireBackendMessage> + Unpin + Send + Sync,
76        C::Error: Debug,
77        PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>,
78    {
79        let query_ctx = self.new_query_context();
80        let db = query_ctx.get_db_string();
81        let _timer = crate::metrics::METRIC_POSTGRES_QUERY_TIMER
82            .with_label_values(&[crate::metrics::METRIC_POSTGRES_SIMPLE_QUERY, db.as_str()])
83            .start_timer();
84
85        if query.is_empty() {
86            // early return if query is empty
87            return Ok(vec![Response::EmptyQuery]);
88        }
89
90        let parsed_query = self.query_parser.compatibility_parser.parse(query);
91
92        let query = if let Ok(statements) = &parsed_query {
93            // Comments, whitespace and empty statements also require EmptyQueryResponse.
94            if statements.is_empty() {
95                return Ok(vec![Response::EmptyQuery]);
96            }
97            statements
98                .iter()
99                .map(statement_to_query)
100                .collect::<Vec<_>>()
101                .join(";")
102        } else {
103            query.to_string()
104        };
105
106        if let Some(resps) = fixtures::process(&query, query_ctx.clone()) {
107            send_warning_opt(client, query_ctx).await?;
108            Ok(resps)
109        } else {
110            let outputs = self.query_handler.do_query(&query, query_ctx.clone()).await;
111
112            let mut results = Vec::with_capacity(outputs.len());
113
114            let statements = parsed_query.ok();
115            for (idx, output) in outputs.into_iter().enumerate() {
116                let copy_format = statements
117                    .as_ref()
118                    .and_then(|stmts| stmts.get(idx))
119                    .and_then(check_copy_to_stdout);
120                let resp = if let Some(format) = &copy_format {
121                    output_to_copy_response(query_ctx.clone(), output, format)?
122                } else {
123                    output_to_query_response(query_ctx.clone(), output, &Format::UnifiedText)?
124                };
125                results.push(resp);
126            }
127
128            send_warning_opt(client, query_ctx).await?;
129            Ok(results)
130        }
131    }
132}
133
134fn statement_to_query(statement: &SqlParserStatement) -> String {
135    if let SqlParserStatement::ShowVariable { variable } = statement
136        && variable.len() > 1
137        && (variable[0].value.eq_ignore_ascii_case("query")
138            || variable[0].value.eq_ignore_ascii_case("datafusion"))
139    {
140        return format!(
141            "SHOW {}",
142            variable
143                .iter()
144                .map(ToString::to_string)
145                .collect::<Vec<_>>()
146                .join(".")
147        );
148    }
149    statement.to_string()
150}
151
152async fn send_warning_opt<C>(client: &mut C, query_context: QueryContextRef) -> PgWireResult<()>
153where
154    C: Sink<PgWireBackendMessage> + Unpin + Send + Sync,
155    C::Error: Debug,
156    PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>,
157{
158    if let Some(warning) = query_context.warning() {
159        client
160            .feed(PgWireBackendMessage::NoticeResponse(
161                ErrorInfo::new(
162                    PgErrorSeverity::Warning.to_string(),
163                    PgErrorCode::Ec01000.code(),
164                    warning.clone(),
165                )
166                .into(),
167            ))
168            .await?;
169    }
170
171    Ok(())
172}
173
174pub(crate) fn output_to_query_response(
175    query_ctx: QueryContextRef,
176    output: Result<Output>,
177    field_format: &Format,
178) -> PgWireResult<Response> {
179    match output {
180        Ok(o) => match o.data {
181            OutputData::AffectedRows(rows) => {
182                Ok(Response::Execution(Tag::new("OK").with_rows(rows)))
183            }
184            OutputData::Stream(record_stream) => {
185                let schema = record_stream.schema();
186                recordbatches_to_query_response(query_ctx, record_stream, schema, field_format)
187            }
188            OutputData::RecordBatches(recordbatches) => {
189                let schema = recordbatches.schema();
190                recordbatches_to_query_response(
191                    query_ctx,
192                    recordbatches.as_stream(),
193                    schema,
194                    field_format,
195                )
196            }
197        },
198        Err(e) => Err(convert_err(e)),
199    }
200}
201
202type RowStream<T> = Pin<Box<dyn Stream<Item = PgWireResult<T>> + Send + Unpin>>;
203
204fn recordbatches_to_query_response<S>(
205    query_ctx: QueryContextRef,
206    recordbatches_stream: S,
207    schema: SchemaRef,
208    field_format: &Format,
209) -> PgWireResult<Response>
210where
211    S: Stream<Item = RecordBatchResult<RecordBatch>> + Send + Unpin + 'static,
212{
213    let format_options = format_options_from_query_ctx(&query_ctx);
214    let pg_schema = Arc::new(
215        schema_to_pg(schema.as_ref(), field_format, Some(format_options)).map_err(convert_err)?,
216    );
217
218    let encoder = DataRowEncoder::new(pg_schema.clone());
219    let row_stream = RecordBatchRowStream::new(
220        query_ctx.clone(),
221        pg_schema.clone(),
222        schema.clone(),
223        recordbatches_stream,
224        encoder,
225    );
226
227    let data_row_stream: RowStream<DataRow> = Box::pin(
228        row_stream
229            .map(move |result| match result {
230                Ok(rows) => Box::pin(stream::iter(rows.into_iter().map(Ok))) as RowStream<DataRow>,
231                Err(e) => Box::pin(stream::once(future::ready(Err(e)))) as RowStream<DataRow>,
232            })
233            .flatten(),
234    );
235
236    Ok(Response::Query(QueryResponse::new(
237        pg_schema,
238        data_row_stream,
239    )))
240}
241
242pub(crate) fn output_to_copy_response(
243    query_ctx: QueryContextRef,
244    output: Result<Output>,
245    format: &str,
246) -> PgWireResult<Response> {
247    match output {
248        Ok(o) => match o.data {
249            OutputData::AffectedRows(_) => Err(PgWireError::UserError(Box::new(ErrorInfo::new(
250                "ERROR".to_string(),
251                "42601".to_string(),
252                "COPY cannot be used with non-query statements".to_string(),
253            )))),
254            OutputData::Stream(record_stream) => {
255                let schema = record_stream.schema();
256                recordbatches_to_copy_response(query_ctx, record_stream, schema, format)
257            }
258            OutputData::RecordBatches(recordbatches) => {
259                let schema = recordbatches.schema();
260                recordbatches_to_copy_response(query_ctx, recordbatches.as_stream(), schema, format)
261            }
262        },
263        Err(e) => Err(convert_err(e)),
264    }
265}
266
267fn recordbatches_to_copy_response<S>(
268    query_ctx: QueryContextRef,
269    recordbatches_stream: S,
270    schema: SchemaRef,
271    format: &str,
272) -> PgWireResult<Response>
273where
274    S: Stream<Item = RecordBatchResult<RecordBatch>> + Send + Unpin + 'static,
275{
276    let format_options = format_options_from_query_ctx(&query_ctx);
277    let pg_fields = schema_to_pg(schema.as_ref(), &Format::UnifiedText, Some(format_options))
278        .map_err(convert_err)?;
279
280    let copy_format = match format.to_lowercase().as_str() {
281        "binary" => 1,
282        _ => 0,
283    };
284
285    let pg_schema = Arc::new(pg_fields);
286    let num_columns = pg_schema.len();
287
288    let copy_encoder = match format.to_lowercase().as_str() {
289        "csv" => CopyEncoder::new_csv(pg_schema.clone(), CopyCsvOptions::default()),
290        "binary" => CopyEncoder::new_binary(pg_schema.clone()),
291        _ => CopyEncoder::new_text(pg_schema.clone(), CopyTextOptions::default()),
292    };
293
294    let row_stream = RecordBatchRowStream::new(
295        query_ctx.clone(),
296        pg_schema.clone(),
297        schema.clone(),
298        recordbatches_stream,
299        copy_encoder,
300    );
301
302    let copy_stream: RowStream<CopyData> = Box::pin(
303        row_stream
304            .map(move |result| match result {
305                Ok(rows) => Box::pin(stream::iter(rows.into_iter().map(Ok))) as RowStream<CopyData>,
306                Err(e) => Box::pin(stream::once(future::ready(Err(e)))) as RowStream<CopyData>,
307            })
308            .flatten(),
309    );
310
311    Ok(Response::CopyOut(CopyResponse::new(
312        copy_format,
313        num_columns,
314        copy_stream,
315    )))
316}
317
318pub struct DefaultQueryParser {
319    query_handler: ServerSqlQueryHandlerRef,
320    session: Arc<Session>,
321    compatibility_parser: PostgresCompatibilityParser,
322}
323
324impl DefaultQueryParser {
325    pub fn new(query_handler: ServerSqlQueryHandlerRef, session: Arc<Session>) -> Self {
326        DefaultQueryParser {
327            query_handler,
328            session,
329            compatibility_parser: PostgresCompatibilityParser::new(),
330        }
331    }
332}
333
334/// A container type of parse result types
335#[derive(Clone, Debug)]
336pub struct PgSqlPlan {
337    pub(crate) plan: SqlPlan,
338    pub(crate) copy_to_stdout_format: Option<String>,
339}
340
341#[async_trait]
342impl QueryParser for DefaultQueryParser {
343    type Statement = PgSqlPlan;
344
345    async fn parse_sql<C>(
346        &self,
347        _client: &C,
348        sql: &str,
349        _types: &[Option<Type>],
350    ) -> PgWireResult<Option<Self::Statement>> {
351        crate::metrics::METRIC_POSTGRES_PREPARED_COUNT.inc();
352        let query_ctx = self.session.new_query_context();
353
354        // do not parse if query is empty or matches rules
355        if sql.is_empty() {
356            return Ok(None);
357        }
358
359        if fixtures::matches(sql) {
360            return Ok(Some(PgSqlPlan {
361                plan: SqlPlan::Shortcut(sql.to_string()),
362                copy_to_stdout_format: None,
363            }));
364        }
365
366        let parsed_statements = self.compatibility_parser.parse(sql);
367        let (sql, copy_to_stdout_format) = if let Ok(mut statements) = parsed_statements {
368            if statements.is_empty() {
369                return Ok(None);
370            }
371            let first_stmt = statements.remove(0);
372            let format = check_copy_to_stdout(&first_stmt);
373            (statement_to_query(&first_stmt), format)
374        } else {
375            // bypass the error: it can run into error because of different
376            // versions of sqlparser
377            (sql.to_string(), None)
378        };
379
380        let mut stmts = ParserContext::create_with_dialect(
381            &sql,
382            &PostgreSqlDialect {},
383            ParseOptions::default(),
384        )
385        .map_err(convert_err)?;
386        if stmts.len() != 1 {
387            Err(PgWireError::UserError(Box::new(ErrorInfo::from(
388                PgErrorCode::Ec42P14,
389            ))))
390        } else {
391            let stmt = stmts.remove(0);
392
393            if let Some(logical_plan) = self
394                .query_handler
395                .do_describe(stmt.clone(), query_ctx)
396                .await
397                .map_err(convert_err)?
398                .map(|DescribeResult { logical_plan }| logical_plan)
399            {
400                Ok(Some(PgSqlPlan {
401                    plan: SqlPlan::Plan(logical_plan, stmt),
402                    copy_to_stdout_format,
403                }))
404            } else {
405                Ok(Some(PgSqlPlan {
406                    plan: SqlPlan::Statement(stmt, sql),
407                    copy_to_stdout_format,
408                }))
409            }
410        }
411    }
412
413    fn get_parameter_types(&self, _stmt: &Self::Statement) -> PgWireResult<Vec<Type>> {
414        // we have our own implementation of describes in ExtendedQueryHandler
415        // so we don't use these methods
416        Err(PgWireError::ApiError(
417            "get_parameter_types is not expected to be called".into(),
418        ))
419    }
420
421    fn get_result_schema(
422        &self,
423        _stmt: &Self::Statement,
424        _column_format: Option<&Format>,
425    ) -> PgWireResult<Vec<FieldInfo>> {
426        // we have our own implementation of describes in ExtendedQueryHandler
427        // so we don't use these methods
428        Err(PgWireError::ApiError(
429            "get_result_schema is not expected to be called".into(),
430        ))
431    }
432}
433
434#[async_trait]
435impl ExtendedQueryHandler for PostgresServerHandlerInner {
436    type Statement = PgSqlPlan;
437    type QueryParser = DefaultQueryParser;
438
439    fn query_parser(&self) -> Arc<Self::QueryParser> {
440        self.query_parser.clone()
441    }
442
443    async fn do_query<C>(
444        &self,
445        client: &mut C,
446        portal: &Portal<Self::Statement>,
447        _max_rows: usize,
448    ) -> PgWireResult<Response>
449    where
450        C: ClientInfo + Sink<PgWireBackendMessage> + Unpin + Send + Sync,
451        C::Error: Debug,
452        PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>,
453    {
454        let query_ctx = self.new_query_context();
455        let db = query_ctx.get_db_string();
456        let _timer = crate::metrics::METRIC_POSTGRES_QUERY_TIMER
457            .with_label_values(&[crate::metrics::METRIC_POSTGRES_EXTENDED_QUERY, db.as_str()])
458            .start_timer();
459
460        let pg_sql_plan = &portal.statement.statement;
461        let sql_plan = &pg_sql_plan.plan;
462
463        let output = match sql_plan {
464            SqlPlan::Empty => {
465                // early return if query is empty
466                return Ok(Response::EmptyQuery);
467            }
468            SqlPlan::Shortcut(query) => {
469                if let Some(mut resps) = fixtures::process(query, query_ctx.clone()) {
470                    send_warning_opt(client, query_ctx).await?;
471                    // if the statement matches our predefined rules, return it early
472                    return Ok(resps.remove(0));
473                } else {
474                    // unreachable logic
475                    return Ok(Response::EmptyQuery);
476                }
477            }
478            SqlPlan::Plan(plan, stmt) => {
479                let values = parameters_to_scalar_values(plan, portal)?;
480                let plan = plan
481                    .clone()
482                    .replace_params_with_values(&ParamValues::List(
483                        values.into_iter().map(Into::into).collect(),
484                    ))
485                    .context(DataFusionSnafu)
486                    .map_err(convert_err)?;
487                self.query_handler
488                    .do_exec_plan(plan, Some(stmt.clone()), query_ctx.clone())
489                    .await
490            }
491            SqlPlan::Statement(_stmt, query) => {
492                // We won't replace params from statement manually any more.
493                // Newer version of datafusion can generate plan for SELECT/INSERT/UPDATE/DELETE.
494                // Only CREATE TABLE and others minor statements cannot generate sql plan,
495                // in this case, we assume these statements will not carry parameters
496                // and execute them directly.
497                self.query_handler
498                    .do_query(query, query_ctx.clone())
499                    .await
500                    .remove(0)
501            }
502        };
503
504        send_warning_opt(client, query_ctx.clone()).await?;
505
506        if let Some(format) = &pg_sql_plan.copy_to_stdout_format {
507            output_to_copy_response(query_ctx, output, format)
508        } else {
509            output_to_query_response(query_ctx, output, &portal.result_column_format)
510        }
511    }
512
513    async fn do_describe_statement<C>(
514        &self,
515        _client: &mut C,
516        stmt: &StoredStatement<Self::Statement>,
517    ) -> PgWireResult<DescribeStatementResponse>
518    where
519        C: ClientInfo + Unpin + Send + Sync,
520    {
521        let sql_plan = &stmt.statement.plan;
522        // client provided parameter types, can be empty if client doesn't try to parse statement
523        let provided_param_types = &stmt.parameter_types;
524        let server_inferenced_types = if let SqlPlan::Plan(plan, _) = &sql_plan {
525            let param_types = DfLogicalPlanner::get_inferred_parameter_types(plan)
526                .context(InferParameterTypesSnafu)
527                .map_err(convert_err)?
528                .into_iter()
529                .map(|(k, v)| (k, v.map(|v| ConcreteDataType::from_arrow_type(&v))))
530                .collect();
531
532            let types = param_types_to_pg_types(&param_types).map_err(convert_err)?;
533
534            Some(types)
535        } else {
536            None
537        };
538
539        let param_count = if provided_param_types.is_empty() {
540            server_inferenced_types
541                .as_ref()
542                .map(|types| types.len())
543                .unwrap_or(0)
544        } else {
545            provided_param_types.len()
546        };
547
548        let param_types = (0..param_count)
549            .map(|i| {
550                let client_type = provided_param_types.get(i);
551                // use server type when client provided type is None (oid: 0 or other invalid values)
552                match client_type {
553                    Some(Some(client_type)) => client_type.clone(),
554                    _ => server_inferenced_types
555                        .as_ref()
556                        .and_then(|types| types.get(i).cloned())
557                        .unwrap_or(Type::UNKNOWN),
558                }
559            })
560            .collect::<Vec<_>>();
561
562        let fields = describe_fields(sql_plan, &Format::UnifiedText, &self.session)?;
563
564        Ok(DescribeStatementResponse::new(param_types, fields))
565    }
566
567    async fn do_describe_portal<C>(
568        &self,
569        _client: &mut C,
570        portal: &Portal<Self::Statement>,
571    ) -> PgWireResult<DescribePortalResponse>
572    where
573        C: ClientInfo + Unpin + Send + Sync,
574    {
575        let sql_plan = &portal.statement.statement.plan;
576        let format = &portal.result_column_format;
577
578        let fields = describe_fields(sql_plan, format, &self.session)?;
579
580        Ok(DescribePortalResponse::new(fields))
581    }
582}
583
584fn describe_fields(
585    sql_plan: &SqlPlan,
586    format: &Format,
587    session: &Arc<Session>,
588) -> PgWireResult<Vec<FieldInfo>> {
589    match sql_plan {
590        // Execution swaps in DistAnalyzeExec (stage/node/plan), whose schema
591        // differs from the logical `Analyze` plan's (plan_type/plan).
592        SqlPlan::Plan(LogicalPlan::Analyze(_), _) => {
593            let schema: Schema =
594                Schema::try_from(dist_analyze_output_schema()).map_err(convert_err)?;
595            schema_to_pg(&schema, format, None).map_err(convert_err)
596        }
597        // query
598        SqlPlan::Plan(plan, _) if !matches!(plan, LogicalPlan::Dml(_) | LogicalPlan::Ddl(_)) => {
599            let schema: Schema = plan.schema().clone().try_into().map_err(convert_err)?;
600            schema_to_pg(&schema, format, None).map_err(convert_err)
601        }
602        // We can cover only part of show statements
603        // these show create statements will return 2 columns
604        SqlPlan::Statement(
605            Statement::ShowCreateDatabase(_)
606            | Statement::ShowCreateTable(_)
607            | Statement::ShowCreateFlow(_)
608            | Statement::ShowCreateView(_),
609            _,
610        ) => Ok(vec![
611            FieldInfo::new(
612                "name".to_string(),
613                None,
614                None,
615                Type::TEXT,
616                format.format_for(0),
617            ),
618            FieldInfo::new(
619                "create_statement".to_string(),
620                None,
621                None,
622                Type::TEXT,
623                format.format_for(1),
624            ),
625        ]),
626        #[cfg(feature = "enterprise")]
627        SqlPlan::Statement(Statement::ShowCreateTrigger(_), _) => Ok(vec![
628            FieldInfo::new(
629                "name".to_string(),
630                None,
631                None,
632                Type::TEXT,
633                format.format_for(0),
634            ),
635            FieldInfo::new(
636                "create_statement".to_string(),
637                None,
638                None,
639                Type::TEXT,
640                format.format_for(1),
641            ),
642        ]),
643        // SHOW FLOW STATUS returns six columns; return their descriptions so
644        // prepared/extended-protocol clients receive the correct row description.
645        SqlPlan::Statement(Statement::ShowFlowStatus(_), _) => Ok(vec![
646            FieldInfo::new(
647                "flow_id".to_string(),
648                None,
649                None,
650                Type::INT8, // matches type_gt_to_pg(UInt32) — do not use INT4
651                format.format_for(0),
652            ),
653            FieldInfo::new(
654                "flow_name".to_string(),
655                None,
656                None,
657                Type::TEXT,
658                format.format_for(1),
659            ),
660            FieldInfo::new(
661                "start_time".to_string(),
662                None,
663                None,
664                Type::TIMESTAMP,
665                format.format_for(2),
666            ),
667            FieldInfo::new(
668                "last_execution_time".to_string(),
669                None,
670                None,
671                Type::TIMESTAMP,
672                format.format_for(3),
673            ),
674            FieldInfo::new(
675                "uptime_seconds".to_string(),
676                None,
677                None,
678                Type::INT8,
679                format.format_for(4),
680            ),
681            FieldInfo::new(
682                "state_size".to_string(),
683                None,
684                None,
685                Type::NUMERIC,
686                format.format_for(5),
687            ),
688        ]),
689
690        #[cfg(feature = "enterprise")]
691        SqlPlan::Statement(Statement::ShowTriggers(_), _) => Ok(vec![FieldInfo::new(
692            "name".to_string(),
693            None,
694            None,
695            Type::TEXT,
696            format.format_for(0),
697        )]),
698        // we will not support other show statements for extended query protocol at least for now.
699        // because the return columns is not predictable at this stage
700        SqlPlan::Shortcut(query) => {
701            // test if query caught by fixture
702            if let Some(mut resp) = fixtures::process(query, session.new_query_context())
703                && let Response::Query(query_response) = resp.remove(0)
704            {
705                Ok((*query_response.row_schema()).clone())
706            } else {
707                // fallback to NoData
708                Ok(vec![])
709            }
710        }
711        // Single column named after the variable (see `query::sql::show_variable`).
712        SqlPlan::Statement(Statement::ShowVariables(show), _) => Ok(vec![FieldInfo::new(
713            show.variable.to_string().to_uppercase(),
714            None,
715            None,
716            Type::TEXT,
717            format.format_for(0),
718        )]),
719        // Mirrors `query::sql::show_status` (currently always empty).
720        SqlPlan::Statement(Statement::ShowStatus(_), _) => Ok(vec![
721            FieldInfo::new(
722                "Variable_name".to_string(),
723                None,
724                None,
725                Type::TEXT,
726                format.format_for(0),
727            ),
728            FieldInfo::new(
729                "Value".to_string(),
730                None,
731                None,
732                Type::TEXT,
733                format.format_for(1),
734            ),
735        ]),
736        SqlPlan::Statement(Statement::ShowSearchPath(_), _) => Ok(vec![FieldInfo::new(
737            "search_path".to_string(),
738            None,
739            None,
740            Type::TEXT,
741            format.format_for(0),
742        )]),
743        // Mirrors `query::sql::describe_table`.
744        SqlPlan::Statement(Statement::DescribeTable(_), _) => {
745            schema_to_pg(&DESCRIBE_TABLE_OUTPUT_SCHEMA, format, None).map_err(convert_err)
746        }
747        // Single column typed with the function's return type (see
748        // `operator::statement::admin_output_schema`).
749        SqlPlan::Statement(Statement::Admin(admin), _) => {
750            let query_ctx = session.new_query_context();
751            match admin_output_schema(admin, &query_ctx) {
752                Some(schema) => schema_to_pg(&schema, format, None).map_err(convert_err),
753                // Unresolvable; execution will surface the error.
754                None => Ok(vec![]),
755            }
756        }
757        // Describe from the declared cursor's schema.
758        SqlPlan::Statement(Statement::FetchCursor(fetch), _) => {
759            let cursor_name = fetch.cursor_name.to_string();
760            match session.get_cursor(&cursor_name) {
761                Some(cursor) => schema_to_pg(&cursor.schema(), format, None).map_err(convert_err),
762                // Cursor not declared yet; execution will error.
763                None => Ok(vec![]),
764            }
765        }
766        _ => {
767            // NoData
768            Ok(vec![])
769        }
770    }
771}
772
773impl ErrorHandler for PostgresServerHandlerInner {
774    fn on_error<C>(&self, _client: &C, error: &mut PgWireError)
775    where
776        C: ClientInfo,
777    {
778        match error {
779            PgWireError::IoError(e) => debug!("Postgres client disconnected: {}", e),
780            _ => info!("Postgres interface error: {}", error),
781        }
782    }
783}
784
785fn check_copy_to_stdout(statement: &SqlParserStatement) -> Option<String> {
786    if let SqlParserStatement::Copy {
787        target, options, ..
788    } = statement
789        && matches!(target, CopyTarget::Stdout)
790    {
791        for opt in options {
792            if let CopyOption::Format(format_ident) = opt {
793                return Some(format_ident.value.to_lowercase());
794            }
795        }
796        return Some("txt".to_string());
797    }
798
799    None
800}
801
802#[cfg(test)]
803mod tests {
804    use datafusion_pg_catalog::sql::PostgresCompatibilityParser;
805
806    use super::*;
807
808    fn parse_copy_statement(sql: &str) -> SqlParserStatement {
809        let parser = PostgresCompatibilityParser::new();
810        let statements = parser.parse(sql).unwrap();
811        statements.into_iter().next().unwrap()
812    }
813
814    #[test]
815    fn test_check_copy_out_with_csv_format() {
816        let statement = parse_copy_statement("COPY (SELECT 1) TO STDOUT WITH (FORMAT CSV)");
817        assert_eq!(check_copy_to_stdout(&statement), Some("csv".to_string()));
818    }
819
820    #[test]
821    fn test_check_copy_out_with_txt_format() {
822        let statement = parse_copy_statement("COPY (SELECT 1) TO STDOUT WITH (FORMAT TXT)");
823        assert_eq!(check_copy_to_stdout(&statement), Some("txt".to_string()));
824    }
825
826    #[test]
827    fn test_check_copy_out_with_binary_format() {
828        let statement = parse_copy_statement("COPY (SELECT 1) TO STDOUT WITH (FORMAT BINARY)");
829        assert_eq!(check_copy_to_stdout(&statement), Some("binary".to_string()));
830    }
831
832    #[test]
833    fn test_check_copy_out_without_format() {
834        let statement = parse_copy_statement("COPY (SELECT 1) TO STDOUT");
835        assert_eq!(check_copy_to_stdout(&statement), Some("txt".to_string()));
836    }
837
838    #[test]
839    fn test_check_copy_out_to_file() {
840        let statement =
841            parse_copy_statement("COPY (SELECT 1) TO '/path/to/file.csv' WITH (FORMAT CSV)");
842        assert_eq!(check_copy_to_stdout(&statement), None);
843    }
844
845    #[test]
846    fn test_check_copy_out_case_insensitive() {
847        let statement = parse_copy_statement("COPY (SELECT 1) TO STDOUT WITH (FORMAT csv)");
848        assert_eq!(check_copy_to_stdout(&statement), Some("csv".to_string()));
849
850        let statement = parse_copy_statement("COPY (SELECT 1) TO STDOUT WITH (FORMAT binary)");
851        assert_eq!(check_copy_to_stdout(&statement), Some("binary".to_string()));
852    }
853
854    #[test]
855    fn test_check_copy_out_with_multiple_options() {
856        let statement = parse_copy_statement(
857            "COPY (SELECT 1) TO STDOUT WITH (FORMAT csv, DELIMITER ',', HEADER)",
858        );
859        assert_eq!(check_copy_to_stdout(&statement), Some("csv".to_string()));
860
861        let statement = parse_copy_statement(
862            "COPY (SELECT 1) TO STDOUT WITH (DELIMITER ',', HEADER, FORMAT binary)",
863        );
864        assert_eq!(check_copy_to_stdout(&statement), Some("binary".to_string()));
865    }
866}