1use 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 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 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) = ©_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#[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 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 (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 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 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 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 return Ok(resps.remove(0));
473 } else {
474 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 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 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(¶m_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 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 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 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 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 SqlPlan::Statement(Statement::ShowFlowStatus(_), _) => Ok(vec![
646 FieldInfo::new(
647 "flow_id".to_string(),
648 None,
649 None,
650 Type::INT8, 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 SqlPlan::Shortcut(query) => {
701 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 Ok(vec![])
709 }
710 }
711 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 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 SqlPlan::Statement(Statement::DescribeTable(_), _) => {
745 schema_to_pg(&DESCRIBE_TABLE_OUTPUT_SCHEMA, format, None).map_err(convert_err)
746 }
747 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 None => Ok(vec![]),
755 }
756 }
757 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 None => Ok(vec![]),
764 }
765 }
766 _ => {
767 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}