chuchi_postgres/row/
mod.rs

1mod from;
2mod to;
3
4use std::{
5	error::Error as StdError,
6	fmt::Write,
7	pin::Pin,
8	task::{Context, Poll},
9};
10
11use futures_util::Stream;
12use pin_project_lite::pin_project;
13use postgres_types::{FromSql, ToSql};
14pub use tokio_postgres::Column;
15use tokio_postgres::row::RowIndex;
16
17use crate::connection::Error;
18
19pub use from::{FromRow, FromRowOwned};
20pub use to::{ToRow, ToRowStatic};
21
22pub trait NamedColumns {
23	/// should return something like "id", "name", "email"
24	fn select_columns() -> &'static str;
25}
26
27#[derive(Debug)]
28#[repr(transparent)]
29pub struct Row {
30	row: tokio_postgres::Row,
31}
32
33impl Row {
34	/// Returns information about the columns of data in the row.
35	pub fn columns(&self) -> &[Column] {
36		self.row.columns()
37	}
38
39	/// Determines if the row contains no values.
40	pub fn is_empty(&self) -> bool {
41		self.row.is_empty()
42	}
43
44	/// Returns the number of values in the row.
45	pub fn len(&self) -> usize {
46		self.row.len()
47	}
48
49	/// Deserializes the row.
50	pub fn deserialize<'a, T>(
51		&'a self,
52	) -> Result<T, Box<dyn StdError + Sync + Send>>
53	where
54		T: FromRow<'a>,
55	{
56		T::from_row(self)
57	}
58
59	/// Deserializes the row and consumes it.
60	///
61	/// todo or deserialize_into?
62	pub fn deserialize_owned<T>(
63		self,
64	) -> Result<T, Box<dyn StdError + Sync + Send>>
65	where
66		T: FromRowOwned,
67	{
68		T::from_row_owned(self)
69	}
70
71	/// Deserializes a value from the row.
72	///
73	/// The value can be specified either by its numeric index in the row, or by its column name.
74	///
75	/// # Panics
76	///
77	/// Panics if the index is out of bounds or if the value cannot be converted to the specified type.
78	pub fn get<'a, I, T>(&'a self, idx: I) -> T
79	where
80		I: RowIndex + std::fmt::Display,
81		T: FromSql<'a>,
82	{
83		self.row.get(idx)
84	}
85
86	/// Like [`Row::get()`], but returns a [`Result`] rather than panicking.
87	pub fn try_get<'a, I, T>(
88		&'a self,
89		idx: I,
90	) -> Result<T, tokio_postgres::Error>
91	where
92		I: RowIndex + std::fmt::Display,
93		T: FromSql<'a>,
94	{
95		self.row.try_get(idx)
96	}
97}
98
99impl From<tokio_postgres::Row> for Row {
100	fn from(row: tokio_postgres::Row) -> Self {
101		Self { row }
102	}
103}
104
105impl FromRowOwned for Row {
106	fn from_row_owned(
107		row: Row,
108	) -> Result<Self, Box<dyn StdError + Sync + Send>> {
109		Ok(row)
110	}
111}
112
113pin_project! {
114	pub struct RowStream {
115		#[pin]
116		inner: tokio_postgres::RowStream,
117	}
118}
119
120impl Stream for RowStream {
121	type Item = Result<Row, Error>;
122
123	fn poll_next(
124		self: Pin<&mut Self>,
125		cx: &mut Context<'_>,
126	) -> Poll<Option<Self::Item>> {
127		let this = self.project();
128
129		match this.inner.poll_next(cx) {
130			Poll::Ready(Some(Ok(row))) => Poll::Ready(Some(Ok(row.into()))),
131			Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e.into()))),
132			Poll::Ready(None) => Poll::Ready(None),
133			Poll::Pending => Poll::Pending,
134		}
135	}
136}
137
138impl From<tokio_postgres::RowStream> for RowStream {
139	fn from(inner: tokio_postgres::RowStream) -> Self {
140		Self { inner }
141	}
142}
143
144#[derive(Debug)]
145pub struct RowBuilder<'a> {
146	inner: Vec<(&'a str, &'a (dyn ToSql + Sync))>,
147}
148
149impl<'a> RowBuilder<'a> {
150	pub fn new() -> Self {
151		Self { inner: Vec::new() }
152	}
153
154	/// Push a new column to the row.
155	///
156	/// ## Note
157	/// Do not use untrusted names this might lead to
158	/// SQL injection.
159	pub fn push(
160		&mut self,
161		name: &'a str,
162		value: &'a (dyn ToSql + Sync),
163	) -> &mut Self {
164		self.inner.push((name, value));
165
166		self
167	}
168}
169
170impl ToRow for RowBuilder<'_> {
171	fn insert_columns(&self, s: &mut String) {
172		for (i, (k, _)) in self.inner.iter().enumerate() {
173			if i != 0 {
174				s.push_str(", ");
175			}
176
177			write!(s, "\"{k}\"").unwrap();
178		}
179	}
180
181	fn insert_values(&self, s: &mut String) {
182		for (i, _) in self.inner.iter().enumerate() {
183			if i != 0 {
184				s.push_str(", ");
185			}
186
187			write!(s, "${}", i + 1).unwrap();
188		}
189	}
190
191	fn update_columns(&self, s: &mut String) {
192		for (i, (k, _)) in self.inner.iter().enumerate() {
193			if i != 0 {
194				s.push_str(", ");
195			}
196
197			write!(s, "\"{k}\" = ${}", i + 1).unwrap();
198		}
199	}
200
201	fn params_len(&self) -> usize {
202		self.inner.len()
203	}
204
205	fn params(&self) -> impl ExactSizeIterator<Item = &(dyn ToSql + Sync)> {
206		self.inner.iter().map(|(_, v)| *v)
207	}
208}
209
210#[cfg(test)]
211mod tests {
212	use super::*;
213
214	#[test]
215	fn test_row_builder() {
216		let mut row = RowBuilder::new();
217		row.push("id", &1i32)
218			.push("name", &"test")
219			.push("email", &"test");
220
221		let mut cols = String::new();
222		row.insert_columns(&mut cols);
223		assert_eq!(cols, r#""id", "name", "email""#);
224
225		let mut values = String::new();
226		row.insert_values(&mut values);
227		assert_eq!(values, r#"$1, $2, $3"#);
228
229		let mut update = String::from("UPDATE \"users\" SET ");
230		row.update_columns(&mut update);
231		assert_eq!(
232			update,
233			r#"UPDATE "users" SET "id" = $1, "name" = $2, "email" = $3"#
234		);
235	}
236}