-
Notifications
You must be signed in to change notification settings - Fork 0
Round 1 of cleaning up SpiTupleTable
#2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: spi-remove-spi-connection
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ use std::ptr::NonNull; | |
|
|
||
| use crate::pg_sys; | ||
|
|
||
| use super::{SpiClient, SpiError, SpiOkCodes, SpiTupleTable}; | ||
| use super::{SpiClient, SpiOkCodes, SpiResult, SpiTupleTable}; | ||
|
|
||
| type CursorName = String; | ||
|
|
||
|
|
@@ -67,18 +67,14 @@ pub struct SpiCursor<'client> { | |
| pub(crate) client: &'client SpiClient, | ||
| } | ||
|
|
||
| impl SpiCursor<'_> { | ||
| impl<'client> SpiCursor<'client> { | ||
| /// Fetch up to `count` rows from the cursor, moving forward | ||
| /// | ||
| /// If `fetch` runs off the end of the available rows, an empty [`SpiTupleTable`] is returned. | ||
| pub fn fetch(&mut self, count: libc::c_long) -> std::result::Result<SpiTupleTable, SpiError> { | ||
| // SAFETY: no concurrent access | ||
| unsafe { | ||
| pg_sys::SPI_tuptable = std::ptr::null_mut(); | ||
| } | ||
|
Comment on lines
-74
to
-78
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There were a couple of these (when I suppose there "should" have been three!). It's not necessary, however. Postgres does this for us in its SPI_cursor_fetch and SPI_execute operation functions. |
||
| pub fn fetch(&mut self, count: libc::c_long) -> SpiResult<SpiTupleTable<'client>> { | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think Note that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm feeling a bit suspicious about the lifetimes here: is SpiCursor itself even actually bound to the lifetime of the SpiClient? https://www.postgresql.org/docs/current/spi-spi-cursor-open.html Not that we cannot or even should not use a shorter lifetime, but it feels unclear here if we are.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't quite gotten to thinking about cursor. It does seem right tho. The results of And I think we need to assume the cursor (Portal) is too, unless I don't think you can read those docs as "the PortalData pointer returned by SPI_cursor_open can outlive the active SPI connection" at least that's my understanding. I have not looked at the Postgres sources around this yet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but I don't mind if we handle the name-indexing case specially, mind, instead of using a lifetime binding for it, as it seems to be better for our sanity to handle it that way, I just want to figure out what's going on. |
||
| // SAFETY: SPI functions to create/find cursors fail via elog, so self.ptr is valid if we successfully set it | ||
| unsafe { pg_sys::SPI_cursor_fetch(self.ptr.as_mut(), true, count) } | ||
| Ok(self.client.prepare_tuple_table(SpiOkCodes::Fetch as i32)?) | ||
| SpiTupleTable::wrap(&self.client, SpiOkCodes::Fetch as i32) | ||
| } | ||
|
|
||
| /// Consume the cursor, returning its name | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,19 +8,56 @@ use crate::memcxt::PgMemoryContexts; | |||||||||||
| use crate::pg_sys::panic::ErrorReportable; | ||||||||||||
| use crate::pg_sys::{self, PgOid}; | ||||||||||||
| use crate::prelude::*; | ||||||||||||
| use crate::spi::SpiClient; | ||||||||||||
|
|
||||||||||||
| use super::{SpiError, SpiErrorCodes, SpiOkCodes, SpiResult}; | ||||||||||||
| use super::{SpiError, SpiErrorCodes, SpiResult}; | ||||||||||||
|
|
||||||||||||
| #[derive(Debug)] | ||||||||||||
| pub struct SpiTupleTable<'client> { | ||||||||||||
| #[allow(dead_code)] | ||||||||||||
| pub(super) status_code: SpiOkCodes, | ||||||||||||
| pub(super) table: Option<&'client mut pg_sys::SPITupleTable>, | ||||||||||||
| pub(super) size: usize, | ||||||||||||
| pub(super) current: isize, | ||||||||||||
| // SpiTupleTable borrows global state setup by the active SpiClient. It doesn't use the client | ||||||||||||
| // directly, but we need to make sure we don't outlive it, so here it is | ||||||||||||
| _client: PhantomData<&'client SpiClient>, | ||||||||||||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had first written this as |
||||||||||||
|
|
||||||||||||
| // and this is that global state. In ::wrap(), this comes from whatever the current value of | ||||||||||||
| // `pg_sys::SPI_tuptable` happens to be. Postgres may change where SPI_tuptable points | ||||||||||||
| // throughout the lifetime of an active SpiClient, but it doesn't mutate (or deallocate) what | ||||||||||||
| // it happens to point to This allows us to have multiple active SpiTupleTables | ||||||||||||
| // within a Spi connection. Whatever this points to is freed via `pg_sys::SPI_freetuptable()` | ||||||||||||
| // when we're dropped. | ||||||||||||
| table: Option<NonNull<pg_sys::SPITupleTable>>, | ||||||||||||
| size: usize, | ||||||||||||
| current: isize, | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| impl<'client> SpiTupleTable<'client> { | ||||||||||||
| /// Wraps the current global `pg_sys::SPI_tuptable` as a new [`SpiTupleTable`] instance, with | ||||||||||||
| /// a lifetime tied to the specified [`SpiClient`]. | ||||||||||||
| pub(super) fn wrap(_client: &'client SpiClient, last_spi_status_code: i32) -> SpiResult<Self> { | ||||||||||||
| Spi::check_status(last_spi_status_code)?; | ||||||||||||
|
|
||||||||||||
| unsafe { | ||||||||||||
| // | ||||||||||||
| // SAFETY: The unsafeness here is that we're accessing static globals. Fortunately, | ||||||||||||
| // Postgres is not multi-threaded so we're okay to do this | ||||||||||||
| // | ||||||||||||
|
Comment on lines
+39
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nervous laughter
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Future Eric is gonna love this comment, if this lands. |
||||||||||||
|
|
||||||||||||
| // different Postgres get the tuptable size different ways | ||||||||||||
| #[cfg(any(feature = "pg11", feature = "pg12"))] | ||||||||||||
| let size = pg_sys::SPI_processed as usize; | ||||||||||||
|
|
||||||||||||
| #[cfg(not(any(feature = "pg11", feature = "pg12")))] | ||||||||||||
| let size = if pg_sys::SPI_tuptable.is_null() { | ||||||||||||
| pg_sys::SPI_processed as usize | ||||||||||||
| } else { | ||||||||||||
| (*pg_sys::SPI_tuptable).numvals as usize | ||||||||||||
| }; | ||||||||||||
|
|
||||||||||||
| let tuptable = pg_sys::SPI_tuptable; | ||||||||||||
|
|
||||||||||||
| Ok(Self { _client: PhantomData, table: NonNull::new(tuptable), size, current: -1 }) | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /// `SpiTupleTable`s are positioned before the start, for iteration purposes. | ||||||||||||
| /// | ||||||||||||
| /// This method moves the position to the first row. If there are no rows, this | ||||||||||||
|
|
@@ -76,9 +113,12 @@ impl<'client> SpiTupleTable<'client> { | |||||||||||
| fn get_spi_tuptable( | ||||||||||||
| &self, | ||||||||||||
| ) -> SpiResult<(*mut pg_sys::SPITupleTable, *mut pg_sys::TupleDescData)> { | ||||||||||||
| let table = self.table.as_deref().ok_or(SpiError::NoTupleTable)?; | ||||||||||||
| // SAFETY: we just assured that `table` is not null | ||||||||||||
| Ok((table as *const _ as *mut _, table.tupdesc)) | ||||||||||||
| let table = self.table.map(|table| table.as_ptr()).ok_or(SpiError::NoTupleTable)?; | ||||||||||||
| let tupdesc = unsafe { | ||||||||||||
| // SAFETY: we just assured that `table` is not null | ||||||||||||
| table.as_mut().unwrap().tupdesc | ||||||||||||
| }; | ||||||||||||
| Ok((table, tupdesc)) | ||||||||||||
|
Comment on lines
+116
to
+121
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. was just trying to make this function readable, esp since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it actually possible for this pointer to be NULL?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes. It is possible for Lines 126 to 130 in b474516
Maybe they're in the wrong place.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, but I suppose... to actually answer your question, in the cases where this function is called, no, it shouldn't be possible for for But I don't know that we can make that true such that we can elide the Option around the NotNull
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be an exceptional condition for it to be NULL -- made it should just panic in that case? Not sure if you're asking because you see/know something I don't or just forcing me to think about it even more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just trying to figure out for myself if the Option is actually mandatory, here. |
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| pub fn get_heap_tuple(&self) -> SpiResult<Option<SpiHeapTupleData<'client>>> { | ||||||||||||
|
|
@@ -298,6 +338,18 @@ impl<'client> Iterator for SpiTupleTable<'client> { | |||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| impl Drop for SpiTupleTable<'_> { | ||||||||||||
| fn drop(&mut self) { | ||||||||||||
| unsafe { | ||||||||||||
| // SAFETY: self.table was created by Postgres from whatever `pg_sys::SPI_tuptable` pointed | ||||||||||||
| // to at the time this SpiTupleTable was constructed | ||||||||||||
| if let Some(ptr) = self.table.take() { | ||||||||||||
| pg_sys::SPI_freetuptable(ptr.as_ptr()) | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
|
Comment on lines
+341
to
+352
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we've been leaking This addresses part of the last bit of this comment: pgcentralfoundation#1209 (comment) |
||||||||||||
| /// Represents a single `pg_sys::Datum` inside a `SpiHeapTupleData` | ||||||||||||
| pub struct SpiHeapTupleDataEntry<'client> { | ||||||||||||
| datum: Option<pg_sys::Datum>, | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code was moved over into the new
SpiTupleTable::wrap()function. Doing so allows us to set its members private, which is much easier to reason about.