From c677605081dd77e32f995f9356c1790a44b29640 Mon Sep 17 00:00:00 2001 From: Michael Greenberg Date: Fri, 13 Feb 2026 11:07:05 -0500 Subject: [PATCH 1/2] remove a repr-types-oriented panic from an assertion --- src/expr/src/relation.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/expr/src/relation.rs b/src/expr/src/relation.rs index 1f0be40e8c51e..b96865e010df2 100644 --- a/src/expr/src/relation.rs +++ b/src/expr/src/relation.rs @@ -1675,7 +1675,9 @@ impl MirRelationExpr { .column_types .iter() .zip_eq(typ.column_types.iter()) - .all(|(t1, t2)| t1.scalar_type.base_eq(&t2.scalar_type)) + .all(|(t1, t2)| t1 + .scalar_type + .base_eq_or_repr_eq_for_assertion(&t2.scalar_type)) ); } let mut typ = typ.unwrap_or_else(|| self.typ()); From 464171147916557ff4a0aa299f5119405c00366a Mon Sep 17 00:00:00 2001 From: Michael Greenberg Date: Fri, 13 Feb 2026 14:05:50 -0500 Subject: [PATCH 2/2] relax assertion in normalize lets, fix base_eq fallback code to handle nullability correctly, add regression test --- src/repr/src/scalar.rs | 8 +- .../sqllogictest/transform/normalize_lets.slt | 134 ++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/src/repr/src/scalar.rs b/src/repr/src/scalar.rs index bfcfac4631ac7..9665e73723c77 100644 --- a/src/repr/src/scalar.rs +++ b/src/repr/src/scalar.rs @@ -3299,7 +3299,13 @@ impl SqlScalarType { } ::tracing::error!("repr type error: base_eq failed for {self:?} and {other:?}"); - ReprScalarType::from(self) == ReprScalarType::from(other) + // SqlScalarType::base_eq does not consider nullability at all, but `ReprScalarType::eq` does + // To reconcile these differences, we check "compatibility", i.e., if we can union the types wthout error. + // Since ReprScalarType::union is a glorified nullability compositor, a successful union means the types + // are equal (modulo nullability) + ReprScalarType::from(self) + .union(&ReprScalarType::from(other)) + .is_ok() } // Determines equality among scalar types that ignores any custom OIDs or diff --git a/test/sqllogictest/transform/normalize_lets.slt b/test/sqllogictest/transform/normalize_lets.slt index 01cda0a607d8c..580cbd061328e 100644 --- a/test/sqllogictest/transform/normalize_lets.slt +++ b/test/sqllogictest/transform/normalize_lets.slt @@ -628,3 +628,137 @@ Used Indexes: Target cluster: quickstart EOF + +## regression test from sqlsmith that crashed an assertion in normalize_lets + +statement ok +EXPLAIN OPTIMIZED PLAN FOR select + mz_internal.mz_session_id() as c0, + pg_catalog.jsonb_build_array() as c1, + ((((0::uint4) & (0::uint4)) # (cast(coalesce(2::uint4, + case when false then null::uint4 else null::uint4 end + ) as uint4))) * (mz_catalog.seahash( + CAST(case when ((0::uint8) <= (null::uint8)) + and (true) then pg_catalog.digest( + CAST(1::text as text), + CAST(2::text as text)) else pg_catalog.digest( + CAST(1::text as text), + CAST(2::text as text)) end + as bytea)))) # (mz_catalog.seahash( + CAST(cast(coalesce(pg_catalog.sha512( + CAST(pg_catalog.sha384( + CAST(cast('\xDEADBEEF' as bytea) as bytea)) as bytea)), + cast(nullif(pg_catalog.hmac( + CAST(cast('\xDEADBEEF' as bytea) as bytea), + CAST(cast('\xDEADBEEF' as bytea) as bytea), + CAST(1::text as text)), + cast('\xFFFFFF' as bytea)) as bytea)) as bytea) as bytea))) as c2, + pg_catalog.makeaclitem( + CAST(mz_internal.aclitem_grantee( + CAST(cast(null as aclitem) as aclitem)) as oid), + CAST(mz_internal.aclitem_grantor( + CAST(case when 87 is not NULL then pg_catalog.makeaclitem( + CAST(mz_internal.aclitem_grantor( + CAST(cast(null as aclitem) as aclitem)) as oid), + CAST(null::oid as oid), + CAST(case when false then null::text else null::text end + as text), + CAST(true as bool)) else pg_catalog.makeaclitem( + CAST(mz_internal.aclitem_grantor( + CAST(cast(null as aclitem) as aclitem)) as oid), + CAST(null::oid as oid), + CAST(case when false then null::text else null::text end + as text), + CAST(true as bool)) end + as aclitem)) as oid), + CAST(case when ((case when (true) + or (false) then 1::text else 1::text end + ) || (pg_catalog.chr( + CAST(68 as int4)))) <= (pg_catalog.session_user()) then cast(nullif((pg_catalog.obj_description( + CAST(null::oid as oid), + CAST(1::text as text))) || (case when (TIME '00:00:00') >= (TIME '01:23:45') then null::text else null::text end + ), + pg_catalog.current_schema()) as text) else cast(nullif((pg_catalog.obj_description( + CAST(null::oid as oid), + CAST(1::text as text))) || (case when (TIME '00:00:00') >= (TIME '01:23:45') then null::text else null::text end + ), + pg_catalog.current_schema()) as text) end + as text), + CAST(case when (cast(coalesce((null::uint4) | (2::uint4), + cast(nullif(0::uint4, + null::uint4) as uint4)) as uint4)) >= (null::uint4) then pg_catalog.pg_has_role( + CAST(cast(0 as oid) as oid), + CAST(cast(coalesce(case when ('{}'::jsonb) = ('[]'::jsonb) then 1::oid else 1::oid end + , + 1::oid) as oid) as oid), + CAST(pg_catalog.current_database() as text)) else pg_catalog.pg_has_role( + CAST(cast(0 as oid) as oid), + CAST(cast(coalesce(case when ('{}'::jsonb) = ('[]'::jsonb) then 1::oid else 1::oid end + , + 1::oid) as oid) as oid), + CAST(pg_catalog.current_database() as text)) end + as bool)) as c3, + pg_catalog.pg_is_in_recovery() as c4, + mz_catalog.mz_now() as c5, + pg_catalog.timezone( + CAST(mz_catalog.mz_version() as text), + CAST(pg_catalog.current_timestamp() as timestamptz)) as c6, + pg_catalog.reverse( + CAST((mz_catalog.mz_version()) || (1::text) as text)) as c7 +from + (select + case when false then mz_internal.mz_role_oid_memberships() else mz_internal.mz_role_oid_memberships() end + as c0, + mz_internal.mz_role_oid_memberships() as c1, + pg_catalog.current_database() as c2, + mz_internal.mz_name_rank( + CAST(pg_catalog.version() as text), + CAST(array['a', 'b', null, '']::text[] as _text), + CAST(case when (cast(0 as bpchar)) > (cast(0 as bpchar)) then 1::text else 1::text end + as text), + CAST(1::text as text)) as c3, + mz_catalog.mz_uptime() as c4, + mz_internal.mz_role_oid_memberships() as c5, + case when false then 58 else 58 end + as c6, + (case when ((2::int8) <> (-9223372036854775808::int8)) + or ((-32768::int2) = (-32768::int2)) then 0::uint8 else 0::uint8 end + ) / (0::uint8) as c7, + mz_internal.mz_role_oid_memberships() as c8, + (cast(coalesce('inf'::float4, + 'nan'::float4) as float4)) - (case when ((67) = (19)) + or (52 is NULL) then 'inf'::float4 else 'inf'::float4 end + ) as c9, + mz_catalog.mz_uptime() as c10 + from + (select + 18 as c0, + 50 as c1 + from + (select + 73 as c0, + 58 as c1, + 24 as c2, + 32 as c3, + 12 as c4 + from + "mz_internal"."mz_session_history" as ref_11 + where true) as subq_8 + where false) as subq_9 + where (array[null, null]) > (pg_catalog.regexp_split_to_array( + CAST(('{"1":2,"3":4}'::jsonb) ->> (9223372036854775807::int8) as text), + CAST(cast(0 as text) as text), + CAST(pg_catalog.session_user() as text)))) as subq_10 +where (mz_internal.aclitem_grantee( + CAST(cast(null as aclitem) as aclitem))) <= (mz_internal.aclitem_grantor( + CAST(pg_catalog.makeaclitem( + CAST(case when (cast(coalesce(cast('\xDEADBEEF' as bytea), + cast('\xDEADBEEF' as bytea)) as bytea)) >= (cast('\xDEADBEEF' as bytea)) then case when ((cast('\xFFFFFF' as bytea)) >= (cast('\xFFFFFF' as bytea))) + or ((61) >= (6)) then 1::oid else 1::oid end + else case when ((cast('\xFFFFFF' as bytea)) >= (cast('\xFFFFFF' as bytea))) + or ((61) >= (6)) then 1::oid else 1::oid end + end + as oid), + CAST(cast(0 as oid) as oid), + CAST(pg_catalog.session_user() as text), + CAST(mz_internal.is_rbac_enabled() as bool)) as aclitem)));