Skip to content

Commit 90cf588

Browse files
committed
test(pgwire): lock binary-format round-trip for narrowed integer OIDs
Extended-query typed reads (i16/i32/i64 via tokio_postgres) against a schemaless collection's SMALLINT/INT/BIGINT columns, asserting both the advertised OIDs (21/23/20) and the decoded values — locking the chain from declared width through RowDescription to the binary value encoders that the OID fix makes reachable. Also corrects an inaccurate sentence in the refinement fn's rustdoc (the keyword-match ordering is consistency with the reference implementation, not load-bearing).
1 parent e0cb183 commit 90cf588

2 files changed

Lines changed: 71 additions & 4 deletions

File tree

nodedb/src/control/server/response_shape/schema.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,12 @@ pub fn sql_data_type_to_ddl_col_type(
9292
/// prefix-with-boundary logic as
9393
/// `pgwire::catalog::tables::collections::field_type_to_oid` (the
9494
/// already-correct reference implementation this mirrors): `BIGINT`/`INT8`
95-
/// are checked *first* so a plain `INT` prefix check can't accidentally
96-
/// swallow `INT8` (there is no separator between `INT` and `8` for the
97-
/// boundary check to reject). An unrecognized or absent `raw` leaves the
98-
/// base `Int8` untouched — refinement is best-effort, never a hard error.
95+
/// are checked *first* to mirror that reference implementation's ordering,
96+
/// but the order isn't load-bearing — the boundary check itself already
97+
/// rejects `int` as a prefix match of `int8`, since the next character
98+
/// (`8`) is neither `(` nor whitespace. An unrecognized or absent `raw`
99+
/// leaves the base `Int8` untouched — refinement is best-effort, never a
100+
/// hard error.
99101
pub fn sql_data_type_to_ddl_col_type_with_raw(
100102
ty: &nodedb_sql::types_expr::SqlDataType,
101103
raw: Option<&str>,

nodedb/tests/pgwire_extended_query_engines.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,68 @@ async fn extended_query_timeseries_null_param() {
320320

321321
assert_eq!(rows.len(), 0, "NULL param must match 0 rows");
322322
}
323+
324+
// ── Schemaless engine (binary round-trip) ────────────────────────────────────
325+
326+
/// Binary-format round-trip for declared integer widths on a schemaless
327+
/// collection. tokio_postgres decodes extended-query results per the
328+
/// RowDescription OID, so reading each column back with a typed getter that
329+
/// matches the *advertised* width (`i16`/`i32`/`i64`) fails the `get` if
330+
/// either the OID or the binary payload width is wrong — this locks the
331+
/// whole chain: declared width → OID 21/23/20 → binary encoder i16/i32/i64
332+
/// (issue #217).
333+
#[tokio::test]
334+
async fn extended_query_schemaless_int_width_binary_roundtrip() {
335+
let srv = TestServer::start().await;
336+
srv.exec("CREATE COLLECTION bin_widths (id TEXT PRIMARY KEY, s SMALLINT, i INT, b BIGINT)")
337+
.await
338+
.expect("CREATE COLLECTION bin_widths");
339+
srv.exec("INSERT INTO bin_widths (id, s, i, b) VALUES ('r1', 123, 456789, 9876543210)")
340+
.await
341+
.expect("INSERT bin_widths");
342+
343+
let stmt = srv
344+
.client
345+
.prepare_typed(
346+
"SELECT id, s, i, b FROM bin_widths WHERE id = $1",
347+
&[Type::TEXT],
348+
)
349+
.await
350+
.expect("prepare bin_widths select");
351+
352+
let rows = srv
353+
.client
354+
.query(&stmt, &[&"r1"])
355+
.await
356+
.expect("execute bin_widths select");
357+
358+
assert_eq!(rows.len(), 1, "expected 1 row");
359+
360+
// Typed getters matching the advertised OIDs — a wrong OID or a
361+
// wrong-width binary payload panics inside `get` before we ever reach
362+
// the assertions below.
363+
let s = rows[0].get::<_, i16>("s");
364+
let i = rows[0].get::<_, i32>("i");
365+
let b = rows[0].get::<_, i64>("b");
366+
assert_eq!(s, 123);
367+
assert_eq!(i, 456789);
368+
assert_eq!(b, 9876543210);
369+
370+
// RowDescription OID check: s→INT2(21), i→INT4(23), b→INT8(20).
371+
let expected: &[(&str, u32)] = &[("s", 21), ("i", 23), ("b", 20)];
372+
for (col_name, expected_oid) in expected {
373+
let col = rows[0]
374+
.columns()
375+
.iter()
376+
.find(|c| c.name() == *col_name)
377+
.unwrap_or_else(|| panic!("column '{col_name}' must appear in RowDescription"));
378+
assert_eq!(
379+
col.type_().oid(),
380+
*expected_oid,
381+
"bin_widths column '{}' OID must be {}, got {}",
382+
col_name,
383+
expected_oid,
384+
col.type_().oid()
385+
);
386+
}
387+
}

0 commit comments

Comments
 (0)