Skip to content

Commit 6cc3ac0

Browse files
committed
refactor: add origin information to Column
1 parent 2692f3c commit 6cc3ac0

File tree

5 files changed

+124
-0
lines changed

5 files changed

+124
-0
lines changed

sqlx-core/src/column.rs

+49
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::database::Database;
22
use crate::error::Error;
33

44
use std::fmt::Debug;
5+
use std::sync::Arc;
56

67
pub trait Column: 'static + Send + Sync + Debug {
78
type Database: Database<Column = Self>;
@@ -20,6 +21,54 @@ pub trait Column: 'static + Send + Sync + Debug {
2021

2122
/// Gets the type information for the column.
2223
fn type_info(&self) -> &<Self::Database as Database>::TypeInfo;
24+
25+
/// If this column comes from a table, return the table and original column name.
26+
///
27+
/// Returns [`ColumnOrigin::Expression`] if the column is the result of an expression
28+
/// or else the source table could not be determined.
29+
///
30+
/// Returns [`ColumnOrigin::Unknown`] if the database driver does not have that information,
31+
/// or has not overridden this method.
32+
fn origin(&self) -> ColumnOrigin { ColumnOrigin::Unknown }
33+
}
34+
35+
/// A [`Column`] that originates from a table.
36+
#[derive(Debug, Clone)]
37+
pub struct TableColumn {
38+
/// The name of the table (optionally schema-qualified) that the column comes from.
39+
pub table: Arc<str>,
40+
/// The original name of the column.
41+
pub name: Arc<str>,
42+
}
43+
44+
/// The possible statuses for our knowledge of the origin of a [`Column`].
45+
#[derive(Debug, Clone)]
46+
pub enum ColumnOrigin {
47+
/// The column is known to originate from a table.
48+
///
49+
/// Included is the table name and original column name.
50+
Table(TableColumn),
51+
/// The column originates from an expression, or else its origin could not be determined.
52+
Expression,
53+
/// The database driver does not know the column origin at this time.
54+
///
55+
/// This may happen if:
56+
/// * The connection is in the middle of executing a query,
57+
/// and cannot query the catalog to fetch this information.
58+
/// * The connection does not have access to the database catalog.
59+
/// * The implementation of [`Column`] did not override [`Column::origin()`].
60+
Unknown,
61+
}
62+
63+
impl ColumnOrigin {
64+
/// Returns the true column origin, if known.
65+
pub fn table_column(&self) -> Option<&TableColumn> {
66+
if let Self::Table(table_column) = self {
67+
Some(table_column)
68+
} else {
69+
None
70+
}
71+
}
2372
}
2473

2574
/// A type that can be used to index into a [`Row`] or [`Statement`].

sqlx-postgres/src/column.rs

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::ext::ustr::UStr;
22
use crate::{PgTypeInfo, Postgres};
33

44
pub(crate) use sqlx_core::column::{Column, ColumnIndex};
5+
use sqlx_core::column::ColumnOrigin;
56

67
#[derive(Debug, Clone)]
78
#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))]
@@ -13,6 +14,8 @@ pub struct PgColumn {
1314
pub(crate) relation_id: Option<crate::types::Oid>,
1415
#[cfg_attr(feature = "offline", serde(skip))]
1516
pub(crate) relation_attribute_no: Option<i16>,
17+
#[cfg_attr(feature = "offline", serde(skip))]
18+
pub(crate) origin: ColumnOrigin,
1619
}
1720

1821
impl PgColumn {
@@ -51,4 +54,8 @@ impl Column for PgColumn {
5154
fn type_info(&self) -> &PgTypeInfo {
5255
&self.type_info
5356
}
57+
58+
fn origin(&self) -> ColumnOrigin {
59+
self.origin.clone()
60+
}
5461
}

sqlx-postgres/src/connection/describe.rs

+59
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::btree_map;
12
use crate::error::Error;
23
use crate::ext::ustr::UStr;
34
use crate::io::StatementId;
@@ -14,6 +15,9 @@ use futures_core::future::BoxFuture;
1415
use smallvec::SmallVec;
1516
use sqlx_core::query_builder::QueryBuilder;
1617
use std::sync::Arc;
18+
use sqlx_core::column::{ColumnOrigin, TableColumn};
19+
use sqlx_core::hash_map;
20+
use crate::connection::TableColumns;
1721

1822
/// Describes the type of the `pg_type.typtype` column
1923
///
@@ -122,13 +126,20 @@ impl PgConnection {
122126
let type_info = self
123127
.maybe_fetch_type_info_by_oid(field.data_type_id, should_fetch)
124128
.await?;
129+
130+
let origin = if let (Some(relation_oid), Some(attribute_no)) = (field.relation_id, field.relation_attribute_no) {
131+
self.maybe_fetch_column_origin(relation_oid, attribute_no, should_fetch).await?
132+
} else {
133+
ColumnOrigin::Expression
134+
};
125135

126136
let column = PgColumn {
127137
ordinal: index,
128138
name: name.clone(),
129139
type_info,
130140
relation_id: field.relation_id,
131141
relation_attribute_no: field.relation_attribute_no,
142+
origin,
132143
};
133144

134145
columns.push(column);
@@ -188,6 +199,54 @@ impl PgConnection {
188199
Ok(PgTypeInfo(PgType::DeclareWithOid(oid)))
189200
}
190201
}
202+
203+
async fn maybe_fetch_column_origin(
204+
&mut self,
205+
relation_id: Oid,
206+
attribute_no: i16,
207+
should_fetch: bool,
208+
) -> Result<ColumnOrigin, Error> {
209+
let mut table_columns = match self.cache_table_to_column_names.entry(relation_id) {
210+
hash_map::Entry::Occupied(table_columns) => {
211+
table_columns.into_mut()
212+
},
213+
hash_map::Entry::Vacant(vacant) => {
214+
if !should_fetch { return Ok(ColumnOrigin::Unknown); }
215+
216+
let table_name: String = query_scalar("SELECT $1::oid::regclass::text")
217+
.bind(relation_id)
218+
.fetch_one(&mut *self)
219+
.await?;
220+
221+
vacant.insert(TableColumns {
222+
table_name: table_name.into(),
223+
columns: Default::default(),
224+
})
225+
}
226+
};
227+
228+
let column_name = match table_columns.columns.entry(attribute_no) {
229+
btree_map::Entry::Occupied(occupied) => Arc::clone(occupied.get()),
230+
btree_map::Entry::Vacant(vacant) => {
231+
if !should_fetch { return Ok(ColumnOrigin::Unknown); }
232+
233+
let column_name: String = query_scalar(
234+
"SELECT attname FROM pg_attribute WHERE attrelid = $1 AND attnum = $2"
235+
)
236+
.bind(relation_id)
237+
.bind(attribute_no)
238+
.fetch_one(&mut *self)
239+
.await?;
240+
241+
Arc::clone(vacant.insert(column_name.into()))
242+
}
243+
};
244+
245+
Ok(ColumnOrigin::Table(TableColumn {
246+
table: table_columns.table_name.clone(),
247+
name: column_name
248+
}))
249+
}
191250

192251
fn fetch_type_by_oid(&mut self, oid: Oid) -> BoxFuture<'_, Result<PgTypeInfo, Error>> {
193252
Box::pin(async move {

sqlx-postgres/src/connection/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::BTreeMap;
12
use std::fmt::{self, Debug, Formatter};
23
use std::sync::Arc;
34

@@ -57,6 +58,7 @@ pub struct PgConnection {
5758
cache_type_info: HashMap<Oid, PgTypeInfo>,
5859
cache_type_oid: HashMap<UStr, Oid>,
5960
cache_elem_type_to_array: HashMap<Oid, Oid>,
61+
cache_table_to_column_names: HashMap<Oid, TableColumns>,
6062

6163
// number of ReadyForQuery messages that we are currently expecting
6264
pub(crate) pending_ready_for_query_count: usize,
@@ -68,6 +70,12 @@ pub struct PgConnection {
6870
log_settings: LogSettings,
6971
}
7072

73+
pub(crate) struct TableColumns {
74+
table_name: Arc<str>,
75+
/// Attribute number -> name.
76+
columns: BTreeMap<i16, Arc<str>>,
77+
}
78+
7179
impl PgConnection {
7280
/// the version number of the server in `libpq` format
7381
pub fn server_version_num(&self) -> Option<u32> {

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub use sqlx_core::acquire::Acquire;
55
pub use sqlx_core::arguments::{Arguments, IntoArguments};
66
pub use sqlx_core::column::Column;
77
pub use sqlx_core::column::ColumnIndex;
8+
pub use sqlx_core::column::ColumnOrigin;
89
pub use sqlx_core::connection::{ConnectOptions, Connection};
910
pub use sqlx_core::database::{self, Database};
1011
pub use sqlx_core::describe::Describe;

0 commit comments

Comments
 (0)