Skip to content

Commit 99ebda2

Browse files
committed
Implement simple query API
The simple query API is a more robust version of `batch_execute`. Like that method, `simple_query` allows passing a `&str` of semicolon delimited queries. Divergence from `batch_execute` is in the return type; instead of nothing, a `Vec<TextRows>` is returned. Each entry in this `Vec` is the result set of one query in the query string. Thus if there are two semicolon delimited queries, there will be two entries in this `Vec`. The `TextRows` and `TextRow` types returned from `simple_query` closely mirror existing `Rows` and `Row` types with one major difference: only string values can be retrieved from them. There are a few TODOs in the code: * Are text values in this case guaranteed to be utf-8 encoded? * unwrap call in simple_query which assumes RowDescription is always sent * documentation (denoted with either STUB or TODO)
1 parent 2200286 commit 99ebda2

File tree

3 files changed

+271
-15
lines changed

3 files changed

+271
-15
lines changed

postgres/src/lib.rs

+87-14
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ use stmt::{Column, Statement};
103103
use tls::TlsHandshake;
104104
use transaction::{IsolationLevel, Transaction};
105105
use types::{Field, FromSql, IsNull, Kind, Oid, ToSql, Type};
106+
use text_rows::TextRows;
106107

107108
#[doc(inline)]
108109
pub use error::Error;
@@ -118,6 +119,7 @@ pub mod notification;
118119
pub mod params;
119120
mod priv_io;
120121
pub mod rows;
122+
pub mod text_rows;
121123
pub mod stmt;
122124
pub mod tls;
123125
pub mod transaction;
@@ -534,18 +536,7 @@ impl InnerConnection {
534536
.and_then(|oid| self.get_type(oid))
535537
.collect()?;
536538

537-
let columns = match raw_columns {
538-
Some(body) => body.fields()
539-
.and_then(|field| {
540-
Ok(Column::new(
541-
field.name().to_owned(),
542-
self.get_type(field.type_oid())?,
543-
))
544-
})
545-
.collect()?,
546-
None => vec![],
547-
};
548-
539+
let columns = self.parse_cols(raw_columns)?;
549540
Ok((param_types, columns))
550541
}
551542

@@ -735,6 +726,22 @@ impl InnerConnection {
735726
Ok(ty)
736727
}
737728

729+
730+
fn parse_cols(&mut self, raw: Option<backend::RowDescriptionBody>) -> Result<Vec<Column>> {
731+
match raw {
732+
Some(body) => body.fields()
733+
.and_then(|field| {
734+
Ok(Column::new(
735+
field.name().to_owned(),
736+
self.get_type(field.type_oid())?,
737+
))
738+
})
739+
.collect()
740+
.map_err(From::from),
741+
None => Ok(vec![]),
742+
}
743+
}
744+
738745
fn setup_typeinfo_query(&mut self) -> Result<()> {
739746
if self.has_typeinfo_query {
740747
return Ok(());
@@ -919,6 +926,53 @@ impl InnerConnection {
919926
}
920927
}
921928

929+
fn simple_query_(&mut self, query: &str) -> Result<Vec<TextRows>> {
930+
check_desync!(self);
931+
debug!("executing query: {}", query);
932+
self.stream.write_message(|buf| frontend::query(query, buf))?;
933+
self.stream.flush()?;
934+
935+
let mut result = vec![];
936+
let mut rows = vec![];
937+
let mut columns = None;
938+
939+
loop {
940+
match self.read_message()? {
941+
backend::Message::ReadyForQuery(_) => break,
942+
backend::Message::DataRow(body) => {
943+
rows.push(RowData::new(body)?);
944+
}
945+
backend::Message::CopyInResponse(_) => {
946+
self.stream.write_message(|buf| {
947+
frontend::copy_fail("COPY queries cannot be directly executed", buf)
948+
})?;
949+
self.stream.write_message(
950+
|buf| Ok::<(), io::Error>(frontend::sync(buf)),
951+
)?;
952+
self.stream.flush()?;
953+
}
954+
backend::Message::ErrorResponse(body) => {
955+
self.wait_for_ready()?;
956+
return Err(err(&mut body.fields()));
957+
}
958+
backend::Message::RowDescription(body) => {
959+
columns = Some(self.parse_cols(Some(body))?);
960+
}
961+
backend::Message::CommandComplete(_) => {
962+
result.push(
963+
TextRows::new(
964+
// TODO is it safe to unwrap here?
965+
columns.take().unwrap(),
966+
mem::replace(&mut rows, Vec::new())
967+
)
968+
);
969+
}
970+
_ => {}
971+
}
972+
}
973+
Ok(result)
974+
}
975+
922976
fn quick_query(&mut self, query: &str) -> Result<Vec<Vec<Option<String>>>> {
923977
check_desync!(self);
924978
debug!("executing query: {}", query);
@@ -1295,6 +1349,14 @@ impl Connection {
12951349
self.0.borrow_mut().quick_query(query).map(|_| ())
12961350
}
12971351

1352+
1353+
/// Send a simple, non-prepared query
1354+
///
1355+
/// TODO docs. Should this replace the batch_execute API?
1356+
pub fn simple_query(&self, query: &str) -> Result<Vec<TextRows>> {
1357+
self.0.borrow_mut().simple_query_(query)
1358+
}
1359+
12981360
/// Returns a structure providing access to asynchronous notifications.
12991361
///
13001362
/// Use the `LISTEN` command to register this connection for notifications.
@@ -1372,6 +1434,9 @@ pub trait GenericConnection {
13721434

13731435
/// Like `Connection::is_active`.
13741436
fn is_active(&self) -> bool;
1437+
1438+
/// Like `Connection::simple_query`.
1439+
fn simple_query(&self, query: &str) -> Result<Vec<TextRows>>;
13751440
}
13761441

13771442
impl GenericConnection for Connection {
@@ -1396,12 +1461,16 @@ impl GenericConnection for Connection {
13961461
}
13971462

13981463
fn batch_execute(&self, query: &str) -> Result<()> {
1399-
self.batch_execute(query)
1464+
self.batch_execute(&query)
14001465
}
14011466

14021467
fn is_active(&self) -> bool {
14031468
self.is_active()
14041469
}
1470+
1471+
fn simple_query(&self, query: &str) -> Result<Vec<TextRows>> {
1472+
self.simple_query(query)
1473+
}
14051474
}
14061475

14071476
impl<'a> GenericConnection for Transaction<'a> {
@@ -1426,7 +1495,11 @@ impl<'a> GenericConnection for Transaction<'a> {
14261495
}
14271496

14281497
fn batch_execute(&self, query: &str) -> Result<()> {
1429-
self.batch_execute(query)
1498+
self.batch_execute(&query)
1499+
}
1500+
1501+
fn simple_query(&self, query: &str) -> Result<Vec<TextRows>> {
1502+
self.simple_query(query)
14301503
}
14311504

14321505
fn is_active(&self) -> bool {

postgres/src/text_rows.rs

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//! Query result rows.
2+
3+
use postgres_shared::rows::RowData;
4+
use std::fmt;
5+
use std::slice;
6+
7+
#[doc(inline)]
8+
pub use postgres_shared::rows::RowIndex;
9+
10+
use stmt::{Column};
11+
12+
/// The resulting rows of a query.
13+
pub struct TextRows {
14+
columns: Vec<Column>,
15+
data: Vec<RowData>,
16+
}
17+
18+
impl fmt::Debug for TextRows {
19+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
20+
fmt.debug_struct("TextRows")
21+
.field("columns", &self.columns())
22+
.field("rows", &self.data.len())
23+
.finish()
24+
}
25+
}
26+
27+
impl TextRows {
28+
pub(crate) fn new(columns: Vec<Column>, data: Vec<RowData>) -> TextRows {
29+
TextRows {
30+
columns: columns,
31+
data: data,
32+
}
33+
}
34+
35+
/// Returns a slice describing the columns of the `TextRows`.
36+
pub fn columns(&self) -> &[Column] {
37+
&self.columns[..]
38+
}
39+
40+
/// Returns the number of rows present.
41+
pub fn len(&self) -> usize {
42+
self.data.len()
43+
}
44+
45+
/// Determines if there are any rows present.
46+
pub fn is_empty(&self) -> bool {
47+
self.len() == 0
48+
}
49+
50+
/// Returns a specific `TextRow`.
51+
///
52+
/// # Panics
53+
///
54+
/// Panics if `idx` is out of bounds.
55+
pub fn get<'a>(&'a self, idx: usize) -> TextRow<'a> {
56+
TextRow {
57+
columns: &self.columns,
58+
data: &self.data[idx],
59+
}
60+
}
61+
62+
/// Returns an iterator over the `TextRow`s.
63+
pub fn iter<'a>(&'a self) -> Iter<'a> {
64+
Iter {
65+
columns: self.columns(),
66+
iter: self.data.iter(),
67+
}
68+
}
69+
}
70+
71+
impl<'a> IntoIterator for &'a TextRows {
72+
type Item = TextRow<'a>;
73+
type IntoIter = Iter<'a>;
74+
75+
fn into_iter(self) -> Iter<'a> {
76+
self.iter()
77+
}
78+
}
79+
80+
/// An iterator over `TextRow`s.
81+
pub struct Iter<'a> {
82+
columns: &'a [Column],
83+
iter: slice::Iter<'a, RowData>,
84+
}
85+
86+
impl<'a> Iterator for Iter<'a> {
87+
type Item = TextRow<'a>;
88+
89+
fn next(&mut self) -> Option<TextRow<'a>> {
90+
self.iter.next().map(|row| {
91+
TextRow {
92+
columns: self.columns,
93+
data: row,
94+
}
95+
})
96+
}
97+
98+
fn size_hint(&self) -> (usize, Option<usize>) {
99+
self.iter.size_hint()
100+
}
101+
}
102+
103+
impl<'a> DoubleEndedIterator for Iter<'a> {
104+
fn next_back(&mut self) -> Option<TextRow<'a>> {
105+
self.iter.next_back().map(|row| {
106+
TextRow {
107+
columns: self.columns,
108+
data: row,
109+
}
110+
})
111+
}
112+
}
113+
114+
impl<'a> ExactSizeIterator for Iter<'a> {}
115+
116+
/// A single result row of a query.
117+
pub struct TextRow<'a> {
118+
columns: &'a [Column],
119+
data: &'a RowData,
120+
}
121+
122+
impl<'a> fmt::Debug for TextRow<'a> {
123+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
124+
fmt.debug_struct("TextRow")
125+
.field("columns", &self.columns)
126+
.finish()
127+
}
128+
}
129+
130+
impl<'a> TextRow<'a> {
131+
/// Returns the number of values in the row.
132+
pub fn len(&self) -> usize {
133+
self.data.len()
134+
}
135+
136+
/// Determines if there are any values in the row.
137+
pub fn is_empty(&self) -> bool {
138+
self.len() == 0
139+
}
140+
141+
/// Returns a slice describing the columns of the `TextRow`.
142+
pub fn columns(&self) -> &[Column] {
143+
self.columns
144+
}
145+
146+
/// stub
147+
pub fn get<I>(&self, idx: I) -> Option<&str>
148+
where
149+
I: RowIndex + fmt::Debug,
150+
{
151+
match self.get_inner(&idx) {
152+
Some(value) => value,
153+
None => panic!("no such column {:?}", idx),
154+
}
155+
}
156+
157+
/// stub
158+
pub fn get_opt<I>(&self, idx: I) -> Option<Option<&str>>
159+
where
160+
I: RowIndex,
161+
{
162+
self.get_inner(&idx)
163+
}
164+
165+
fn get_inner<I>(&self, idx: &I) -> Option<Option<&str>>
166+
where
167+
I: RowIndex,
168+
{
169+
let idx = match idx.__idx(self.columns) {
170+
Some(idx) => idx,
171+
None => return None,
172+
};
173+
174+
// TODO can we assume these values will always be utf8?
175+
Some(self.data.get(idx).map(|s| ::std::str::from_utf8(s).expect("utf8 encoded")))
176+
}
177+
}

postgres/src/transaction.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::cell::Cell;
44
use std::fmt;
55

66
use rows::Rows;
7+
use text_rows::TextRows;
78
use stmt::Statement;
89
use types::ToSql;
910
use {bad_response, Connection, Result};
@@ -229,6 +230,11 @@ impl<'conn> Transaction<'conn> {
229230
self.conn.batch_execute(query)
230231
}
231232

233+
/// Like `Connection::simple_query`.
234+
pub fn simple_query(&self, query: &str) -> Result<Vec<TextRows>> {
235+
self.conn.simple_query(query)
236+
}
237+
232238
/// Like `Connection::transaction`, but creates a nested transaction via
233239
/// a savepoint.
234240
///
@@ -277,7 +283,7 @@ impl<'conn> Transaction<'conn> {
277283
pub fn set_config(&self, config: &Config) -> Result<()> {
278284
let mut command = "SET TRANSACTION".to_owned();
279285
config.build_command(&mut command);
280-
self.batch_execute(&command)
286+
self.batch_execute(&command).map(|_| ())
281287
}
282288

283289
/// Determines if the transaction is currently set to commit or roll back.

0 commit comments

Comments
 (0)