Skip to content

Commit d04aea8

Browse files
committed
Problem: use of Option<Parent> in SubTransaction
It is only there so that we can move it out of manually copied type that implements Drop Solution: extract release logic into a separate trait
1 parent df654a1 commit d04aea8

File tree

2 files changed

+142
-94
lines changed

2 files changed

+142
-94
lines changed

pgx-tests/src/tests/subxact_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ mod tests {
5757
Spi::execute(|c| {
5858
c.update("CREATE TABLE a (v INTEGER)", None, None);
5959
// The type below is explicit to ensure it's commit on drop by default
60-
c.sub_transaction(|xact: SubTransaction<SpiClient, true>| {
60+
c.sub_transaction(|xact: SubTransaction<SpiClient, CommitOnDrop>| {
6161
xact.update("INSERT INTO a VALUES (0)", None, None);
6262
// Dropped explicitly for illustration purposes
6363
drop(xact);
@@ -81,7 +81,7 @@ mod tests {
8181
Spi::execute(|c| {
8282
c.update("CREATE TABLE a (v INTEGER)", None, None);
8383
// The type below is explicit to ensure it's commit on drop by default
84-
c.sub_transaction(|xact: SubTransaction<SpiClient, true>| {
84+
c.sub_transaction(|xact: SubTransaction<SpiClient, CommitOnDrop>| {
8585
xact.update("INSERT INTO a VALUES (0)", None, None);
8686
let xact = xact.rollback_on_drop();
8787
// Dropped explicitly for illustration purposes

pgx/src/subxact.rs

Lines changed: 140 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,152 +2,200 @@ use crate::{pg_sys, PgMemoryContexts, SpiClient};
22
use std::fmt::Debug;
33
use std::ops::Deref;
44

5-
/// Sub-transaction
6-
///
7-
/// Can be created by calling `SpiClient::sub_transaction`, `SubTransaction<Parent>::sub_transaction`
8-
/// or any other implementation of `SubTransactionExt` and obtaining it as an argument to the provided closure.
9-
///
10-
/// Unless rolled back or committed explicitly, it'll commit if `COMMIT` generic parameter is `true`
11-
/// (default) or roll back if it is `false`.
12-
#[derive(Debug)]
13-
pub struct SubTransaction<Parent: SubTransactionExt, const COMMIT: bool = true> {
5+
/// Releases a sub-transaction on Drop
6+
pub trait ReleaseOnDrop {}
7+
8+
/// Sub-transaction's contextual information
9+
#[derive(Clone, Copy)]
10+
pub struct Context {
1411
memory_context: pg_sys::MemoryContext,
1512
// Resource ownership before the transaction
1613
//
1714
// Based on information from src/backend/utils/resowner/README
1815
// as well as practical use of it in src/pl/plpython/plpy_spi.c
1916
resource_owner: pg_sys::ResourceOwner,
20-
// Should the transaction be released, or was it already committed or rolled back?
21-
//
22-
// The reason we are not calling this `released` as we're also using this flag when
23-
// we convert between commit_on_drop and rollback_on_drop to ensure it doesn't get released
24-
// on the drop of the original value.
25-
should_release: bool,
26-
parent: Option<Parent>,
2717
}
2818

29-
impl<Parent: SubTransactionExt, const COMMIT: bool> SubTransaction<Parent, COMMIT> {
30-
/// Create a new sub-transaction.
31-
fn new(parent: Parent) -> Self {
19+
impl Context {
20+
/// Captures the context
21+
fn capture() -> Self {
3222
// Remember the memory context before starting the sub-transaction
33-
let ctx = PgMemoryContexts::CurrentMemoryContext.value();
23+
let memory_context = PgMemoryContexts::CurrentMemoryContext.value();
3424
// Remember resource owner before starting the sub-transaction
3525
let resource_owner = unsafe { pg_sys::CurrentResourceOwner };
36-
unsafe {
37-
pg_sys::BeginInternalSubTransaction(std::ptr::null() /* [no] transaction name */);
38-
}
39-
// Switch to the outer memory context so that all allocations remain
40-
// there instead of the sub-transaction's context
41-
PgMemoryContexts::For(ctx).set_as_current();
42-
Self { memory_context: ctx, should_release: true, resource_owner, parent: Some(parent) }
26+
Self { memory_context, resource_owner }
4327
}
28+
}
4429

45-
/// Commit the transaction, returning its parent
46-
pub fn commit(mut self) -> Parent {
47-
self.internal_commit();
48-
self.should_release = false;
49-
self.parent.take().unwrap()
30+
impl From<Context> for CommitOnDrop {
31+
fn from(context: Context) -> Self {
32+
CommitOnDrop(context)
5033
}
34+
}
5135

52-
/// Rollback the transaction, returning its parent
53-
pub fn rollback(mut self) -> Parent {
54-
self.internal_rollback();
55-
self.should_release = false;
56-
self.parent.take().unwrap()
36+
impl From<Context> for RollbackOnDrop {
37+
fn from(context: Context) -> Self {
38+
RollbackOnDrop(context)
5739
}
40+
}
5841

59-
fn internal_rollback(&self) {
42+
/// Commits a sub-transaction on Drop
43+
pub struct CommitOnDrop(Context);
44+
45+
impl Drop for CommitOnDrop {
46+
fn drop(&mut self) {
6047
unsafe {
61-
pg_sys::RollbackAndReleaseCurrentSubTransaction();
62-
pg_sys::CurrentResourceOwner = self.resource_owner;
48+
pg_sys::ReleaseCurrentSubTransaction();
49+
pg_sys::CurrentResourceOwner = self.0.resource_owner;
6350
}
64-
PgMemoryContexts::For(self.memory_context).set_as_current();
51+
PgMemoryContexts::For(self.0.memory_context).set_as_current();
6552
}
53+
}
6654

67-
fn internal_commit(&self) {
55+
impl ReleaseOnDrop for CommitOnDrop {}
56+
57+
/// Rolls back a sub-transaction on Drop
58+
pub struct RollbackOnDrop(Context);
59+
60+
impl Drop for RollbackOnDrop {
61+
fn drop(&mut self) {
6862
unsafe {
69-
pg_sys::ReleaseCurrentSubTransaction();
70-
pg_sys::CurrentResourceOwner = self.resource_owner;
63+
pg_sys::RollbackAndReleaseCurrentSubTransaction();
64+
pg_sys::CurrentResourceOwner = self.0.resource_owner;
7165
}
72-
PgMemoryContexts::For(self.memory_context).set_as_current();
66+
PgMemoryContexts::For(self.0.memory_context).set_as_current();
7367
}
7468
}
7569

76-
impl<Parent: SubTransactionExt> SubTransaction<Parent, true> {
77-
/// Make this sub-transaction roll back on drop
78-
pub fn rollback_on_drop(self) -> SubTransaction<Parent, false> {
79-
self.into()
70+
impl ReleaseOnDrop for RollbackOnDrop {}
71+
72+
impl Into<RollbackOnDrop> for CommitOnDrop {
73+
fn into(self) -> RollbackOnDrop {
74+
let result = RollbackOnDrop(self.0);
75+
// IMPORTANT: avoid running Drop (that would commit)
76+
std::mem::forget(self);
77+
result
8078
}
8179
}
8280

83-
impl<Parent: SubTransactionExt> SubTransaction<Parent, false> {
84-
/// Make this sub-transaction commit on drop
85-
pub fn commit_on_drop(self) -> SubTransaction<Parent, true> {
86-
self.into()
81+
impl Into<CommitOnDrop> for RollbackOnDrop {
82+
fn into(self) -> CommitOnDrop {
83+
let result = CommitOnDrop(self.0);
84+
// IMPORTANT: avoid running Drop (that would roll back)
85+
std::mem::forget(self);
86+
result
8787
}
8888
}
8989

90-
impl<Parent: SubTransactionExt> Into<SubTransaction<Parent, false>>
91-
for SubTransaction<Parent, true>
90+
struct NoOpOnDrop;
91+
92+
impl ReleaseOnDrop for NoOpOnDrop {}
93+
94+
/// Sub-transaction
95+
///
96+
/// Can be created by calling `SpiClient::sub_transaction`, `SubTransaction<Parent>::sub_transaction`
97+
/// or any other implementation of `SubTransactionExt` and obtaining it as an argument to the provided closure.
98+
///
99+
/// Unless rolled back or committed explicitly, it'll commit if `Release` generic parameter is `CommitOnDrop`
100+
/// (default) or roll back if it is `RollbackOnDrop`.
101+
#[derive(Debug)]
102+
pub struct SubTransaction<Parent: SubTransactionExt, Release: ReleaseOnDrop = CommitOnDrop> {
103+
release: Release,
104+
parent: Parent,
105+
}
106+
107+
impl<Parent: SubTransactionExt, Release: ReleaseOnDrop> SubTransaction<Parent, Release>
108+
where
109+
Release: From<Context>,
92110
{
93-
fn into(mut self) -> SubTransaction<Parent, false> {
94-
let result = SubTransaction {
95-
memory_context: self.memory_context,
96-
resource_owner: self.resource_owner,
97-
should_release: self.should_release,
98-
parent: self.parent.take(),
99-
};
100-
// Make sure original sub-transaction won't commit
101-
self.should_release = false;
102-
result
111+
/// Create a new sub-transaction.
112+
fn new(parent: Parent) -> Self {
113+
let context = Context::capture();
114+
let memory_context = context.memory_context;
115+
let release = context.into();
116+
unsafe {
117+
pg_sys::BeginInternalSubTransaction(std::ptr::null() /* [no] transaction name */);
118+
}
119+
// Switch to the outer memory context so that all allocations remain
120+
// there instead of the sub-transaction's context
121+
PgMemoryContexts::For(memory_context).set_as_current();
122+
Self { release, parent }
103123
}
104124
}
105125

106-
impl<Parent: SubTransactionExt> Into<SubTransaction<Parent, true>>
107-
for SubTransaction<Parent, false>
108-
{
109-
fn into(mut self) -> SubTransaction<Parent, true> {
110-
let result = SubTransaction {
111-
memory_context: self.memory_context,
112-
resource_owner: self.resource_owner,
113-
should_release: self.should_release,
114-
parent: self.parent.take(),
115-
};
116-
// Make sure original sub-transaction won't roll back
117-
self.should_release = false;
118-
result
126+
impl<Parent: SubTransactionExt> SubTransaction<Parent, CommitOnDrop> {
127+
/// Commit the transaction, returning its parent
128+
pub fn commit(self) -> Parent {
129+
// `Self::do_nothing_on_drop()` will commit as `Release` is `CommitOnDrop`
130+
self.do_nothing_on_drop().parent
119131
}
120132
}
121133

122-
impl<Parent: SubTransactionExt, const COMMIT: bool> Drop for SubTransaction<Parent, COMMIT> {
123-
fn drop(&mut self) {
124-
if self.should_release {
125-
if COMMIT {
126-
self.internal_commit();
127-
} else {
128-
self.internal_rollback();
129-
}
130-
}
134+
impl<Parent: SubTransactionExt> SubTransaction<Parent, RollbackOnDrop> {
135+
/// Commit the transaction, returning its parent
136+
pub fn commit(self) -> Parent {
137+
// Make sub-transaction commit on drop and then use `commit`
138+
self.commit_on_drop().commit()
139+
}
140+
}
141+
142+
impl<Parent: SubTransactionExt> SubTransaction<Parent, RollbackOnDrop> {
143+
/// Rollback the transaction, returning its parent
144+
pub fn rollback(self) -> Parent {
145+
// `Self::do_nothing_on_drop()` will roll back as `Release` is `RollbackOnDrop`
146+
self.do_nothing_on_drop().parent
147+
}
148+
}
149+
150+
impl<Parent: SubTransactionExt> SubTransaction<Parent, CommitOnDrop> {
151+
/// Rollback the transaction, returning its parent
152+
pub fn rollback(self) -> Parent {
153+
// Make sub-transaction roll back on drop and then use `rollback`
154+
self.rollback_on_drop().rollback()
155+
}
156+
}
157+
158+
impl<Parent: SubTransactionExt> SubTransaction<Parent, CommitOnDrop> {
159+
/// Make this sub-transaction roll back on drop
160+
pub fn rollback_on_drop(self) -> SubTransaction<Parent, RollbackOnDrop> {
161+
SubTransaction { parent: self.parent, release: self.release.into() }
162+
}
163+
}
164+
165+
impl<Parent: SubTransactionExt> SubTransaction<Parent, RollbackOnDrop> {
166+
/// Make this sub-transaction commit on drop
167+
pub fn commit_on_drop(self) -> SubTransaction<Parent, CommitOnDrop> {
168+
SubTransaction { parent: self.parent, release: self.release.into() }
169+
}
170+
}
171+
172+
impl<Parent: SubTransactionExt, Release: ReleaseOnDrop> SubTransaction<Parent, Release> {
173+
/// Make this sub-transaction do nothing on drop
174+
///
175+
/// Releases the sub-transaction based on `Release` generic parameter. Further
176+
/// dropping of the sub-transaction will not do anything.
177+
fn do_nothing_on_drop(self) -> SubTransaction<Parent, NoOpOnDrop> {
178+
SubTransaction { parent: self.parent, release: NoOpOnDrop }
131179
}
132180
}
133181

134182
// This allows SubTransaction to be de-referenced to SpiClient
135-
impl<'conn, const COMMIT: bool> Deref for SubTransaction<SpiClient<'conn>, COMMIT> {
183+
impl<'conn, Release: ReleaseOnDrop> Deref for SubTransaction<SpiClient<'conn>, Release> {
136184
type Target = SpiClient<'conn>;
137185

138186
fn deref(&self) -> &Self::Target {
139-
self.parent.as_ref().unwrap()
187+
&self.parent
140188
}
141189
}
142190

143191
// This allows a SubTransaction of a SubTransaction to be de-referenced to SpiClient
144-
impl<Parent: SubTransactionExt, const COMMIT: bool> Deref
145-
for SubTransaction<SubTransaction<Parent>, COMMIT>
192+
impl<Parent: SubTransactionExt, Release: ReleaseOnDrop> Deref
193+
for SubTransaction<SubTransaction<Parent>, Release>
146194
{
147195
type Target = Parent;
148196

149197
fn deref(&self) -> &Self::Target {
150-
self.parent.as_ref().and_then(|p| p.parent.as_ref()).unwrap()
198+
&self.parent.parent
151199
}
152200
}
153201

0 commit comments

Comments
 (0)