vortex_fsst/compute/
compare.rs1use vortex_array::ArrayRef;
5use vortex_array::ArrayView;
6use vortex_array::ExecutionCtx;
7use vortex_array::IntoArray;
8use vortex_array::arrays::BoolArray;
9use vortex_array::arrays::ConstantArray;
10use vortex_array::builtins::ArrayBuiltins;
11use vortex_array::dtype::DType;
12use vortex_array::scalar::Scalar;
13use vortex_array::scalar_fn::fns::binary::CompareKernel;
14use vortex_array::scalar_fn::fns::operators::CompareOperator;
15use vortex_array::scalar_fn::fns::operators::Operator;
16use vortex_buffer::BitBuffer;
17use vortex_buffer::ByteBuffer;
18use vortex_error::VortexExpect;
19use vortex_error::VortexResult;
20use vortex_error::vortex_bail;
21
22use crate::FSST;
23use crate::FSSTArrayExt;
24use crate::FSSTArraySlotsExt;
25impl CompareKernel for FSST {
26 fn compare(
27 lhs: ArrayView<'_, Self>,
28 rhs: &ArrayRef,
29 operator: CompareOperator,
30 ctx: &mut ExecutionCtx,
31 ) -> VortexResult<Option<ArrayRef>> {
32 match rhs.as_constant() {
33 Some(constant) => compare_fsst_constant(lhs, &constant, operator, ctx),
34 _ => Ok(None),
36 }
37 }
38}
39
40fn compare_fsst_constant(
42 left: ArrayView<'_, FSST>,
43 right: &Scalar,
44 operator: CompareOperator,
45 ctx: &mut ExecutionCtx,
46) -> VortexResult<Option<ArrayRef>> {
47 let is_rhs_empty = match right.dtype() {
48 DType::Binary(_) => right
49 .as_binary()
50 .is_empty()
51 .vortex_expect("RHS should not be null"),
52 DType::Utf8(_) => right
53 .as_utf8()
54 .is_empty()
55 .vortex_expect("RHS should not be null"),
56 _ => vortex_bail!("VarBinArray can only have type of Binary or Utf8"),
57 };
58 if is_rhs_empty {
59 let buffer = match operator {
60 CompareOperator::Gte => BitBuffer::new_set(left.len()),
62 CompareOperator::Lt => BitBuffer::new_unset(left.len()),
64 _ => left
65 .uncompressed_lengths()
66 .binary(
67 ConstantArray::new(
68 Scalar::zero_value(left.uncompressed_lengths().dtype()),
69 left.uncompressed_lengths().len(),
70 )
71 .into_array(),
72 operator.into(),
73 )?
74 .execute(ctx)?,
75 };
76
77 return Ok(Some(
78 BoolArray::new(
79 buffer,
80 left.array()
81 .validity()?
82 .union_nullability(right.dtype().nullability()),
83 )
84 .into_array(),
85 ));
86 }
87
88 if !matches!(operator, CompareOperator::Eq | CompareOperator::NotEq) {
90 return Ok(None);
91 }
92
93 let compressor = left.compressor();
94 let encoded_buffer = match left.dtype() {
95 DType::Utf8(_) => {
96 let value = right
97 .as_utf8()
98 .value()
99 .vortex_expect("Expected non-null scalar");
100 ByteBuffer::from(compressor.compress(value.as_bytes()))
101 }
102 DType::Binary(_) => {
103 let value = right
104 .as_binary()
105 .value()
106 .vortex_expect("Expected non-null scalar");
107 ByteBuffer::from(compressor.compress(value.as_slice()))
108 }
109 _ => unreachable!("FSSTArray can only have string or binary data type"),
110 };
111
112 let encoded_scalar = Scalar::binary(
113 encoded_buffer,
114 left.dtype().nullability() | right.dtype().nullability(),
115 );
116
117 let rhs = ConstantArray::new(encoded_scalar, left.len());
118 left.codes()
119 .into_array()
120 .binary(rhs.into_array(), Operator::from(operator))
121 .map(Some)
122}
123
124#[cfg(test)]
125mod tests {
126 use std::sync::LazyLock;
127
128 use vortex_array::IntoArray;
129 use vortex_array::VortexSessionExecute;
130 use vortex_array::arrays::BoolArray;
131 use vortex_array::arrays::ConstantArray;
132 use vortex_array::arrays::VarBinArray;
133 use vortex_array::assert_arrays_eq;
134 use vortex_array::builtins::ArrayBuiltins;
135 use vortex_array::dtype::DType;
136 use vortex_array::dtype::Nullability;
137 use vortex_array::scalar::Scalar;
138 use vortex_array::scalar_fn::fns::operators::Operator;
139 use vortex_error::VortexResult;
140 use vortex_session::VortexSession;
141
142 use crate::fsst_compress;
143 use crate::fsst_train_compressor;
144
145 static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
146 let session = vortex_array::array_session();
147 crate::initialize(&session);
148 session
149 });
150
151 #[test]
152 #[cfg_attr(miri, ignore)]
153 fn test_compare_fsst() -> VortexResult<()> {
154 let mut ctx = SESSION.create_execution_ctx();
155 let lhs = VarBinArray::from_iter(
156 [
157 Some("hello"),
158 None,
159 Some("world"),
160 None,
161 Some("this is a very long string"),
162 ],
163 DType::Utf8(Nullability::Nullable),
164 )
165 .into_array();
166 let compressor = fsst_train_compressor(&lhs, &mut ctx)?;
167 let lhs = fsst_compress(&lhs, &compressor, &mut ctx)?;
168
169 let rhs = ConstantArray::new("world", lhs.len());
170
171 let equals = lhs
173 .clone()
174 .into_array()
175 .binary(rhs.clone().into_array(), Operator::Eq)?
176 .execute::<BoolArray>(&mut ctx)?;
177
178 assert_eq!(equals.dtype(), &DType::Bool(Nullability::Nullable));
179
180 assert_arrays_eq!(
181 &equals,
182 &BoolArray::from_iter([Some(false), None, Some(true), None, Some(false)]),
183 &mut ctx
184 );
185
186 let not_equals = lhs
188 .clone()
189 .into_array()
190 .binary(rhs.into_array(), Operator::NotEq)?
191 .execute::<BoolArray>(&mut ctx)?;
192
193 assert_eq!(not_equals.dtype(), &DType::Bool(Nullability::Nullable));
194 assert_arrays_eq!(
195 ¬_equals,
196 &BoolArray::from_iter([Some(true), None, Some(false), None, Some(true)]),
197 &mut ctx
198 );
199
200 let null_rhs =
202 ConstantArray::new(Scalar::null(DType::Utf8(Nullability::Nullable)), lhs.len());
203 let equals_null = lhs
204 .clone()
205 .into_array()
206 .binary(null_rhs.clone().into_array(), Operator::Eq)?;
207 assert_arrays_eq!(
208 &equals_null,
209 &BoolArray::from_iter([None::<bool>, None, None, None, None]),
210 &mut ctx
211 );
212
213 let noteq_null = lhs
214 .into_array()
215 .binary(null_rhs.into_array(), Operator::NotEq)?;
216 assert_arrays_eq!(
217 ¬eq_null,
218 &BoolArray::from_iter([None::<bool>, None, None, None, None]),
219 &mut ctx
220 );
221 Ok(())
222 }
223}