Skip to content

Commit 1efef02

Browse files
committed
fix(postgres): syntax error in EXPLAIN query
1 parent e731cfd commit 1efef02

File tree

3 files changed

+58
-27
lines changed

3 files changed

+58
-27
lines changed

sqlx-postgres/src/connection/describe.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,11 @@ WHERE rngtypid = $1
500500
stmt_id: StatementId,
501501
params_len: usize,
502502
) -> Result<Vec<Option<bool>>, Error> {
503-
let mut explain = format!("EXPLAIN (VERBOSE, FORMAT JSON) EXECUTE {stmt_id}");
503+
let stmt_id_display = stmt_id
504+
.display()
505+
.ok_or_else(|| err_protocol!("cannot EXPLAIN unnamed statement: {stmt_id:?}"))?;
506+
507+
let mut explain = format!("EXPLAIN (VERBOSE, FORMAT JSON) EXECUTE {stmt_id_display}");
504508
let mut comma = false;
505509

506510
if params_len > 0 {

sqlx-postgres/src/io/buf_mut.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,12 @@ impl PgBufMutExt for Vec<u8> {
4747
// writes a statement name by ID
4848
#[inline]
4949
fn put_statement_name(&mut self, id: StatementId) {
50-
let _: Result<(), ()> = id.write_name(|s| {
51-
self.extend_from_slice(s.as_bytes());
52-
Ok(())
53-
});
50+
id.put_name_with_nul(self);
5451
}
5552

5653
// writes a portal name by ID
5754
#[inline]
5855
fn put_portal_name(&mut self, id: PortalId) {
59-
let _: Result<(), ()> = id.write_name(|s| {
60-
self.extend_from_slice(s.as_bytes());
61-
Ok(())
62-
});
56+
id.put_name_with_nul(self);
6357
}
6458
}

sqlx-postgres/src/io/mod.rs

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ pub(crate) struct PortalId(IdInner);
1616
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1717
struct IdInner(Option<NonZeroU32>);
1818

19+
pub(crate) struct DisplayId {
20+
prefix: &'static str,
21+
id: NonZeroU32,
22+
}
23+
1924
impl StatementId {
2025
#[allow(dead_code)]
2126
pub const UNNAMED: Self = Self(IdInner::UNNAMED);
@@ -34,17 +39,23 @@ impl StatementId {
3439
pub fn name_len(&self) -> Saturating<usize> {
3540
self.0.name_len(Self::NAME_PREFIX)
3641
}
42+
43+
/// Get a type to format this statement ID with [`Display`].
44+
///
45+
/// Returns `None` if this is the unnamed statement.
46+
#[inline(always)]
47+
pub fn display(&self) -> Option<DisplayId> {
48+
self.0.display(Self::NAME_PREFIX)
49+
}
3750

38-
// There's no common trait implemented by `Formatter` and `Vec<u8>` for this purpose;
39-
// we're deliberately avoiding the formatting machinery because it's known to be slow.
40-
pub fn write_name<E>(&self, write: impl FnMut(&str) -> Result<(), E>) -> Result<(), E> {
41-
self.0.write_name(Self::NAME_PREFIX, write)
51+
pub fn put_name_with_nul(&self, buf: &mut Vec<u8>) {
52+
self.0.put_name_with_nul(Self::NAME_PREFIX, buf)
4253
}
4354
}
4455

45-
impl Display for StatementId {
56+
impl Display for DisplayId {
4657
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
47-
self.write_name(|s| f.write_str(s))
58+
write!(f, "{}{}", self.prefix, self.id)
4859
}
4960
}
5061

@@ -67,13 +78,13 @@ impl PortalId {
6778
Self(self.0.next())
6879
}
6980

70-
/// Calculate the number of bytes that will be written by [`Self::write_name()`].
81+
/// Calculate the number of bytes that will be written by [`Self::put_name_with_nul()`].
7182
pub fn name_len(&self) -> Saturating<usize> {
7283
self.0.name_len(Self::NAME_PREFIX)
7384
}
7485

75-
pub fn write_name<E>(&self, write: impl FnMut(&str) -> Result<(), E>) -> Result<(), E> {
76-
self.0.write_name(Self::NAME_PREFIX, write)
86+
pub fn put_name_with_nul(&self, buf: &mut Vec<u8>) {
87+
self.0.put_name_with_nul(Self::NAME_PREFIX, buf)
7788
}
7889
}
7990

@@ -92,7 +103,15 @@ impl IdInner {
92103
.map(|id| id.checked_add(1).unwrap_or(NonZeroU32::MIN)),
93104
)
94105
}
95-
106+
107+
#[inline(always)]
108+
fn display(&self, prefix: &'static str) -> Option<DisplayId> {
109+
self.0.map(|id| DisplayId {
110+
prefix,
111+
id
112+
})
113+
}
114+
96115
#[inline(always)]
97116
fn name_len(&self, name_prefix: &str) -> Saturating<usize> {
98117
let mut len = Saturating(0);
@@ -113,18 +132,32 @@ impl IdInner {
113132
}
114133

115134
#[inline(always)]
116-
fn write_name<E>(
135+
fn put_name_with_nul(
117136
&self,
118137
name_prefix: &str,
119-
mut write: impl FnMut(&str) -> Result<(), E>,
120-
) -> Result<(), E> {
138+
buf: &mut Vec<u8>,
139+
) {
121140
if let Some(id) = self.0 {
122-
write(name_prefix)?;
123-
write(itoa::Buffer::new().format(id.get()))?;
141+
buf.extend_from_slice(name_prefix.as_bytes());
142+
buf.extend_from_slice(itoa::Buffer::new().format(id.get()).as_bytes());
124143
}
125144

126-
write("\0")?;
127-
128-
Ok(())
145+
buf.push(0);
129146
}
130147
}
148+
149+
#[test]
150+
fn statement_id_display_matches_encoding() {
151+
const EXPECTED_STR: &str = "sqlx_s_1234567890";
152+
const EXPECTED_BYTES: &[u8] = b"sqlx_s_1234567890\0";
153+
154+
let mut bytes = Vec::new();
155+
156+
StatementId::TEST_VAL.put_name_with_nul(&mut bytes);
157+
158+
assert_eq!(bytes, EXPECTED_BYTES);
159+
160+
let str = StatementId::TEST_VAL.display().unwrap().to_string();
161+
162+
assert_eq!(str, EXPECTED_STR);
163+
}

0 commit comments

Comments
 (0)