Describe the bug
For the hardcoded array fast-paths in extract.rs (INT2_ARRAY, INT4_ARRAY, INT8_ARRAY, TEXT_ARRAY/VARCHAR_ARRAY, FLOAT4_ARRAY, FLOAT8_ARRAY, BOOL_ARRAY), an array containing even one NULL element decodes to null for the entire column value instead of [value, null].
Root cause: these fast-paths decode via Vec<T>: FromSql (e.g. Vec<i32>), not Vec<Option<T>>. tokio-postgres's Vec<T>::from_sql calls T::from_sql_nullable per element and propagates an error the moment any element is absent, since plain T has no "missing" representation. try_extract's catch-all then turns that error into JsonValue::Null for the whole column.
To Reproduce
SELECT ARRAY[1, NULL]; -- int4[] -> returns null, not [1, null]
SELECT ARRAY['a', NULL]; -- text[] -> returns null, not ["a", null]
SELECT ARRAY[true, NULL]; -- bool[] -> returns null, not [true, null]
Discovered via
Found while regression-testing #71/#72 (hstore and array-of-custom-OID-type fixes). Confirmed via git stash that this behavior is unchanged by those fixes — it exists identically on main. Notably, the new generic array decoder added in #72 (for enum[]/hstore[]/etc.) does not have this bug — it correctly decodes ARRAY['happy'::mood, NULL] as ["happy", null], since it parses each element's length prefix directly (-1 length = null) rather than going through Vec<T>: FromSql.
Suggested fix
Change the hardcoded fast-paths to decode via Vec<Option<T>> instead of Vec<T>, mapping each Option<T> to JsonValue (null-safe) instead of T directly. Matches the builtin driver's extract/array.rs, which fills a Null slot per failed/absent element rather than nulling the whole array (see extract_recursively_or_fill_nulls_into's fill_nulls fallback).
Relevant Log Output
N/A — no error, just silent whole-array null.
Describe the bug
For the hardcoded array fast-paths in
extract.rs(INT2_ARRAY,INT4_ARRAY,INT8_ARRAY,TEXT_ARRAY/VARCHAR_ARRAY,FLOAT4_ARRAY,FLOAT8_ARRAY,BOOL_ARRAY), an array containing even oneNULLelement decodes tonullfor the entire column value instead of[value, null].Root cause: these fast-paths decode via
Vec<T>: FromSql(e.g.Vec<i32>), notVec<Option<T>>. tokio-postgres'sVec<T>::from_sqlcallsT::from_sql_nullableper element and propagates an error the moment any element is absent, since plainThas no "missing" representation.try_extract's catch-all then turns that error intoJsonValue::Nullfor the whole column.To Reproduce
Discovered via
Found while regression-testing #71/#72 (hstore and array-of-custom-OID-type fixes). Confirmed via
git stashthat this behavior is unchanged by those fixes — it exists identically onmain. Notably, the new generic array decoder added in #72 (forenum[]/hstore[]/etc.) does not have this bug — it correctly decodesARRAY['happy'::mood, NULL]as["happy", null], since it parses each element's length prefix directly (-1length = null) rather than going throughVec<T>: FromSql.Suggested fix
Change the hardcoded fast-paths to decode via
Vec<Option<T>>instead ofVec<T>, mapping eachOption<T>toJsonValue(null-safe) instead ofTdirectly. Matches the builtin driver'sextract/array.rs, which fills aNullslot per failed/absent element rather than nulling the whole array (seeextract_recursively_or_fill_nulls_into'sfill_nullsfallback).Relevant Log Output
N/A — no error, just silent whole-array
null.