Skip to content

Commit c961267

Browse files
authored
pg_sys::Oid::from_datum() should cast to u32 (#1991)
As implemented in Postgres, the OID version of a `Datum` is a cast of the Datum's `uintptr_t` value to a `u32`, not a bounds checked version of that pointer value. Related, the `PgRelation::from_datum()` implementation is actually a REGCLASS behind the scenes, which is an OID, and it was similarly bugged. This is kinda hard to write a test for as you'd need a relation of some kind in the schema with an OID over i32::MAX, and that's really tough to materialize without mucking around with the `pg_resetwal` tool, which is way outside the scope of what our test suite can handle.
1 parent 4d3ca8f commit c961267

File tree

4 files changed

+61
-5
lines changed

4 files changed

+61
-5
lines changed

pgrx-tests/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ mod log_tests;
4343
mod memcxt_tests;
4444
mod name_tests;
4545
mod numeric_tests;
46+
mod oid_tests;
4647
mod pg_cast_tests;
4748
mod pg_extern_tests;
4849
mod pg_guard_tests;

pgrx-tests/src/tests/oid_tests.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2+
//LICENSE
3+
//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4+
//LICENSE
5+
//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <[email protected]>
6+
//LICENSE
7+
//LICENSE All rights reserved.
8+
//LICENSE
9+
//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10+
#[cfg(any(test, feature = "pg_test"))]
11+
#[pgrx::pg_schema]
12+
mod tests {
13+
#[allow(unused_imports)]
14+
use crate as pgrx_tests;
15+
16+
use pgrx::prelude::*;
17+
18+
#[pg_extern]
19+
fn oid_roundtrip(oid: pg_sys::Oid) -> pg_sys::Oid {
20+
oid
21+
}
22+
23+
#[pg_test]
24+
fn test_reasonable_oid() -> spi::Result<()> {
25+
let oid = Spi::get_one::<pg_sys::Oid>("SELECT tests.oid_roundtrip(42)")?
26+
.expect("SPI result was null");
27+
assert_eq!(oid.as_u32(), 42);
28+
Ok(())
29+
}
30+
31+
#[pg_test]
32+
fn test_completely_unreasonable_but_still_valid_oid() -> spi::Result<()> {
33+
// nb: this stupid value is greater than i32::MAX
34+
let oid = Spi::get_one::<pg_sys::Oid>("SELECT tests.oid_roundtrip(2147483648)")?
35+
.expect("SPI result was null");
36+
assert_eq!(oid.as_u32(), 2_147_483_648);
37+
Ok(())
38+
}
39+
}

pgrx/src/datum/from.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,23 @@ impl FromDatum for pg_sys::Oid {
188188
if is_null {
189189
None
190190
} else {
191-
datum.value().try_into().ok().map(|uint: u32| pg_sys::Oid::from(uint))
191+
// NB: Postgres' `DatumGetObjectId()` function is defined as a straight cast
192+
// rather than assuming the Datum's pointer value is itself a valid unsigned int:
193+
//
194+
// ```c
195+
// /*
196+
// * DatumGetObjectId
197+
// * Returns object identifier value of a datum.
198+
// */
199+
// static inline Oid
200+
// DatumGetObjectId(Datum X)
201+
// {
202+
// return (Oid) X;
203+
// }
204+
// ```
205+
206+
let oid_as_u32 = datum.value() as u32;
207+
Some(pg_sys::Oid::from(oid_as_u32))
192208
}
193209
}
194210
}

pgrx/src/rel.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,10 +308,10 @@ impl FromDatum for PgRelation {
308308
if is_null {
309309
None
310310
} else {
311-
Some(PgRelation::with_lock(
312-
pg_sys::Oid::from(u32::try_from(datum.value()).ok()?),
313-
pg_sys::AccessShareLock as pg_sys::LOCKMODE,
314-
))
311+
// the `PgRelation` SQL type is `REGCLASS`, which is just an `OID`, so that's how
312+
// we'll get the value.
313+
let oid = pg_sys::Oid::from_datum(datum, false)?;
314+
Some(PgRelation::with_lock(oid, pg_sys::AccessShareLock as pg_sys::LOCKMODE))
315315
}
316316
}
317317
}

0 commit comments

Comments
 (0)