1use vortex_error::VortexResult;
5use vortex_error::vortex_bail;
6use vortex_mask::Mask;
7
8use crate::ArrayRef;
9use crate::Executable;
10use crate::ExecutionCtx;
11use crate::IntoArray;
12use crate::arrays::BoolArray;
13use crate::columnar::Columnar;
14use crate::dtype::DType;
15use crate::dtype::Nullability;
16use crate::validity::Validity;
17
18impl Executable for Mask {
19 fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
25 if !matches!(array.dtype(), DType::Bool(Nullability::NonNullable)) {
26 vortex_bail!(
27 "Mask array must have boolean(NonNullable) dtype, not {}",
28 array.dtype()
29 );
30 }
31
32 let array_len = array.len();
33 Ok(match array.execute(ctx)? {
34 Columnar::Constant(s) => {
35 Mask::new(array_len, s.scalar().as_bool().value().unwrap_or(false))
36 }
37 Columnar::Canonical(a) => {
38 let bool = a.into_array().execute::<BoolArray>(ctx)?;
39 Mask::from(bool.into_bit_buffer())
40 }
41 })
42 }
43}
44
45pub struct NullAsFalse(ArrayRef);
51
52impl ArrayRef {
53 pub fn null_as_false(self) -> NullAsFalse {
56 NullAsFalse(self)
57 }
58}
59
60impl NullAsFalse {
61 pub fn execute(self, ctx: &mut ExecutionCtx) -> VortexResult<Mask> {
66 let array = self.0;
67 if !matches!(array.dtype(), DType::Bool(_)) {
68 vortex_bail!("Mask array must have boolean dtype, not {}", array.dtype());
69 }
70 if !array.dtype().is_nullable() {
72 return array.execute::<Mask>(ctx);
73 }
74
75 let len = array.len();
76 Ok(match array.execute::<Columnar>(ctx)? {
77 Columnar::Constant(c) => Mask::new(len, c.scalar().as_bool().value().unwrap_or(false)),
78 Columnar::Canonical(c) => {
79 let bool = c.into_array().execute::<BoolArray>(ctx)?;
80 match bool.as_ref().validity()? {
81 Validity::NonNullable | Validity::AllValid => {
82 Mask::from_buffer(bool.into_bit_buffer())
83 }
84 Validity::AllInvalid => Mask::new_false(len),
85 Validity::Array(v) => {
86 let validity_bits = v.execute::<BoolArray>(ctx)?.into_bit_buffer();
87 Mask::from_buffer(bool.into_bit_buffer() & &validity_bits)
88 }
89 }
90 }
91 })
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use vortex_error::VortexResult;
98 use vortex_mask::Mask;
99
100 use crate::ExecutionCtx;
101 use crate::IntoArray;
102 use crate::VortexSessionExecute;
103 use crate::array_session;
104 use crate::arrays::BoolArray;
105 use crate::arrays::ConstantArray;
106 use crate::builtins::ArrayBuiltins;
107 use crate::dtype::DType;
108 use crate::dtype::Nullability;
109 use crate::scalar::Scalar;
110
111 fn ctx() -> ExecutionCtx {
112 array_session().create_execution_ctx()
113 }
114
115 #[test]
116 fn mask_non_nullable() -> VortexResult<()> {
117 let array = BoolArray::from_iter([true, false, true]).into_array();
118 let mask = array.execute::<Mask>(&mut ctx())?;
119 assert_eq!(mask, Mask::from_iter([true, false, true]));
120 Ok(())
121 }
122
123 #[test]
124 fn mask_rejects_nullable() {
125 let array = BoolArray::from_iter([Some(true), None]).into_array();
126 assert!(array.execute::<Mask>(&mut ctx()).is_err());
127 }
128
129 #[test]
130 fn fill_null_then_mask_coerces_nulls() -> VortexResult<()> {
131 let array = BoolArray::from_iter([Some(true), None, Some(false), None]).into_array();
132 let mask = array.fill_null(false)?.execute::<Mask>(&mut ctx())?;
133 assert_eq!(mask, Mask::from_iter([true, false, false, false]));
134 Ok(())
135 }
136
137 #[test]
138 fn fill_null_then_mask_null_constant() -> VortexResult<()> {
139 let array =
140 ConstantArray::new(Scalar::null(DType::Bool(Nullability::Nullable)), 4).into_array();
141 let mask = array.fill_null(false)?.execute::<Mask>(&mut ctx())?;
142 assert_eq!(mask, Mask::new_false(4));
143 Ok(())
144 }
145
146 #[test]
147 fn null_as_false_non_nullable() -> VortexResult<()> {
148 let array = BoolArray::from_iter([true, false, true]).into_array();
149 let mask = array.null_as_false().execute(&mut ctx())?;
150 assert_eq!(mask, Mask::from_iter([true, false, true]));
151 Ok(())
152 }
153
154 #[test]
155 fn null_as_false_treats_null_as_false() -> VortexResult<()> {
156 let array = BoolArray::from_iter([Some(true), None, Some(false), None]).into_array();
157 let mask = array.null_as_false().execute(&mut ctx())?;
158 assert_eq!(mask, Mask::from_iter([true, false, false, false]));
159 Ok(())
160 }
161
162 #[test]
163 fn null_as_false_null_constant() -> VortexResult<()> {
164 let array =
165 ConstantArray::new(Scalar::null(DType::Bool(Nullability::Nullable)), 4).into_array();
166 let mask = array.null_as_false().execute(&mut ctx())?;
167 assert_eq!(mask, Mask::new_false(4));
168 Ok(())
169 }
170
171 #[test]
172 fn null_as_false_matches_fill_null_then_mask() -> VortexResult<()> {
173 let array =
174 BoolArray::from_iter([Some(true), None, Some(false), Some(true), None]).into_array();
175 let via_fill_null = array.fill_null(false)?.execute::<Mask>(&mut ctx())?;
176 let via_coerce = array.null_as_false().execute(&mut ctx())?;
177 assert_eq!(via_coerce, via_fill_null);
178 Ok(())
179 }
180}