1use std::sync::Arc;
2
3#[cfg(not(feature = "datafusion"))]
4use arrow::{datatypes::*, record_batch::RecordBatch};
5#[cfg(feature = "postgis")]
6use arrow_schema::extension::ExtensionType;
7#[cfg(feature = "datafusion")]
8use datafusion::arrow::{datatypes::*, record_batch::RecordBatch};
9
10use pgwire::api::Type;
11use pgwire::api::portal::Format;
12use pgwire::api::results::FieldInfo;
13use pgwire::error::{ErrorInfo, PgWireError, PgWireResult};
14use pgwire::messages::data::DataRow;
15use pgwire::types::format::FormatOptions;
16use postgres_types::Kind;
17
18use crate::row_encoder::RowEncoder;
19
20#[cfg(feature = "datafusion")]
21pub mod df;
22
23pub fn into_pg_type(arrow_type: &DataType) -> PgWireResult<Type> {
24 let datatype = match arrow_type {
25 DataType::Null => Type::UNKNOWN,
26 DataType::Boolean => Type::BOOL,
27 DataType::Int8 => Type::INT2,
28 DataType::Int16 | DataType::UInt8 => Type::INT2,
29 DataType::Int32 | DataType::UInt16 => Type::INT4,
30 DataType::Int64 | DataType::UInt32 => Type::INT8,
31 DataType::UInt64 => Type::NUMERIC,
32 DataType::Timestamp(_, tz) => {
33 if tz.is_some() {
34 Type::TIMESTAMPTZ
35 } else {
36 Type::TIMESTAMP
37 }
38 }
39 DataType::Time32(_) | DataType::Time64(_) => Type::TIME,
40 DataType::Date32 | DataType::Date64 => Type::DATE,
41 DataType::Interval(_) | DataType::Duration(_) => Type::INTERVAL,
42 DataType::Binary
43 | DataType::FixedSizeBinary(_)
44 | DataType::LargeBinary
45 | DataType::BinaryView => Type::BYTEA,
46 DataType::Float16 | DataType::Float32 => Type::FLOAT4,
47 DataType::Float64 => Type::FLOAT8,
48 DataType::Decimal128(_, _) => Type::NUMERIC,
49 DataType::Utf8 | DataType::LargeUtf8 | DataType::Utf8View => Type::TEXT,
50 DataType::List(field)
51 | DataType::FixedSizeList(field, _)
52 | DataType::LargeList(field)
53 | DataType::ListView(field)
54 | DataType::LargeListView(field) => match field.data_type() {
55 DataType::Null => Type::TEXT_ARRAY,
59 DataType::Boolean => Type::BOOL_ARRAY,
60 DataType::Int8 => Type::INT2_ARRAY,
61 DataType::Int16 | DataType::UInt8 => Type::INT2_ARRAY,
62 DataType::Int32 | DataType::UInt16 => Type::INT4_ARRAY,
63 DataType::Int64 | DataType::UInt32 => Type::INT8_ARRAY,
64 DataType::UInt64 | DataType::Decimal128(_, _) => Type::NUMERIC_ARRAY,
65 DataType::Timestamp(_, tz) => {
66 if tz.is_some() {
67 Type::TIMESTAMPTZ_ARRAY
68 } else {
69 Type::TIMESTAMP_ARRAY
70 }
71 }
72 DataType::Time32(_) | DataType::Time64(_) => Type::TIME_ARRAY,
73 DataType::Date32 | DataType::Date64 => Type::DATE_ARRAY,
74 DataType::Interval(_) | DataType::Duration(_) => Type::INTERVAL_ARRAY,
75 DataType::FixedSizeBinary(_)
76 | DataType::Binary
77 | DataType::LargeBinary
78 | DataType::BinaryView => Type::BYTEA_ARRAY,
79 DataType::Float16 | DataType::Float32 => Type::FLOAT4_ARRAY,
80 DataType::Float64 => Type::FLOAT8_ARRAY,
81 DataType::Utf8 | DataType::LargeUtf8 | DataType::Utf8View => Type::TEXT_ARRAY,
82 DataType::Struct(_) => Type::new(
83 Type::RECORD_ARRAY.name().into(),
84 Type::RECORD_ARRAY.oid(),
85 Kind::Array(field_into_pg_type(field)?),
86 Type::RECORD_ARRAY.schema().into(),
87 ),
88 list_type => {
89 return Err(PgWireError::UserError(Box::new(ErrorInfo::new(
90 "ERROR".to_owned(),
91 "XX000".to_owned(),
92 format!("Unsupported List Datatype {list_type}"),
93 ))));
94 }
95 },
96 DataType::Dictionary(_, value_type) => into_pg_type(value_type.as_ref())?,
97 DataType::Struct(fields) => {
98 let name: String = fields
99 .iter()
100 .map(|x| x.name().clone())
101 .reduce(|a, b| a + ", " + &b)
102 .map(|x| format!("({x})"))
103 .unwrap_or("()".to_string());
104 let kind = Kind::Composite(
105 fields
106 .iter()
107 .map(|x| {
108 field_into_pg_type(x)
109 .map(|_type| postgres_types::Field::new(x.name().clone(), _type))
110 })
111 .collect::<Result<Vec<_>, PgWireError>>()?,
112 );
113 Type::new(name, Type::RECORD.oid(), kind, Type::RECORD.schema().into())
114 }
115 _ => {
116 return Err(PgWireError::UserError(Box::new(ErrorInfo::new(
117 "ERROR".to_owned(),
118 "XX000".to_owned(),
119 format!("Unsupported Datatype {arrow_type}"),
120 ))));
121 }
122 };
123
124 Ok(datatype)
125}
126
127pub fn field_into_pg_type(field: &Arc<Field>) -> PgWireResult<Type> {
128 let arrow_type = field.data_type();
129
130 match field.extension_type_name() {
131 #[cfg(feature = "postgis")]
137 Some(geoarrow_schema::PointType::NAME) => Ok(Type::TEXT),
138 #[cfg(feature = "postgis")]
139 Some(geoarrow_schema::LineStringType::NAME) => Ok(Type::TEXT),
140 #[cfg(feature = "postgis")]
141 Some(geoarrow_schema::PolygonType::NAME) => Ok(Type::TEXT),
142 #[cfg(feature = "postgis")]
143 Some(geoarrow_schema::MultiPointType::NAME) => Ok(Type::TEXT),
144 #[cfg(feature = "postgis")]
145 Some(geoarrow_schema::MultiLineStringType::NAME) => Ok(Type::TEXT),
146 #[cfg(feature = "postgis")]
147 Some(geoarrow_schema::MultiPolygonType::NAME) => Ok(Type::TEXT),
148 #[cfg(feature = "postgis")]
149 Some(geoarrow_schema::GeometryCollectionType::NAME) => Ok(Type::TEXT),
150 #[cfg(feature = "postgis")]
151 Some(geoarrow_schema::GeometryType::NAME) => Ok(Type::TEXT),
152 #[cfg(feature = "postgis")]
153 Some(geoarrow_schema::RectType::NAME) => Ok(Type::TEXT),
154 #[cfg(feature = "postgis")]
155 Some(geoarrow_schema::WktType::NAME) => Ok(Type::TEXT),
156 #[cfg(feature = "postgis")]
157 Some(geoarrow_schema::WkbType::NAME) => Ok(Type::TEXT),
158
159 _ => into_pg_type(arrow_type),
160 }
161}
162
163pub fn arrow_schema_to_pg_fields(
164 schema: &Schema,
165 format: &Format,
166 data_format_options: Option<Arc<FormatOptions>>,
167) -> PgWireResult<Vec<FieldInfo>> {
168 let _ = data_format_options;
169 schema
170 .fields()
171 .iter()
172 .enumerate()
173 .map(|(idx, f)| {
174 let pg_type = field_into_pg_type(f)?;
175 let mut field_info =
176 FieldInfo::new(f.name().into(), None, None, pg_type, format.format_for(idx));
177 if let Some(data_format_options) = &data_format_options {
178 field_info = field_info.with_format_options(data_format_options.clone());
179 }
180
181 Ok(field_info)
182 })
183 .collect::<PgWireResult<Vec<FieldInfo>>>()
184}
185
186pub fn encode_recordbatch(
187 fields: Arc<Vec<FieldInfo>>,
188 record_batch: RecordBatch,
189) -> Box<impl Iterator<Item = PgWireResult<DataRow>>> {
190 let mut row_stream = RowEncoder::new(record_batch, fields);
191 Box::new(std::iter::from_fn(move || row_stream.next_row()))
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn null_list_is_text_array() {
200 let ty = DataType::List(Arc::new(Field::new_list_field(DataType::Null, true)));
203 assert_eq!(into_pg_type(&ty).unwrap(), Type::TEXT_ARRAY);
204 }
205}