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