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