@@ -10,14 +10,10 @@ use std::fmt::Formatter;
1010use std:: sync:: Arc ;
1111
1212use itertools:: Itertools ;
13- use num_traits:: AsPrimitive ;
14- use vortex_buffer:: Buffer ;
15- use vortex_buffer:: BufferMut ;
1613use vortex_error:: VortexResult ;
1714use vortex_error:: vortex_bail;
1815use vortex_error:: vortex_ensure;
1916use vortex_error:: vortex_ensure_eq;
20- use vortex_mask:: AllOr ;
2117use vortex_session:: registry:: CachedId ;
2218
2319use crate :: ArrayRef ;
@@ -26,25 +22,20 @@ use crate::IntoArray;
2622use crate :: arrays:: List ;
2723use crate :: arrays:: ListArray ;
2824use crate :: arrays:: ListViewArray ;
29- use crate :: arrays:: PrimitiveArray ;
3025use crate :: arrays:: list:: ListArrayExt ;
3126use crate :: arrays:: list:: ListArraySlotsExt ;
3227use crate :: arrays:: listview:: ListViewArrayExt ;
3328use crate :: arrays:: listview:: ListViewArraySlotsExt ;
34- use crate :: arrays:: listview:: ListViewRebuildMode ;
35- use crate :: arrays:: listview:: list_from_list_view;
3629use crate :: dtype:: DType ;
3730use crate :: dtype:: FieldName ;
3831use crate :: expr:: BoundLambda ;
3932use crate :: expr:: Lambda ;
4033use crate :: expr:: display:: ExprDisplay ;
41- use crate :: match_each_integer_ptype;
4234use crate :: scalar_fn:: Arity ;
4335use crate :: scalar_fn:: ChildName ;
4436use crate :: scalar_fn:: ExecutionArgs ;
4537use crate :: scalar_fn:: ScalarFnId ;
4638use crate :: scalar_fn:: ScalarFnVTable ;
47- use crate :: validity:: Validity ;
4839
4940/// Apply a lambda to every element of each list, keeping each list's length and validity.
5041///
@@ -140,12 +131,10 @@ impl ScalarFnVTable for ListTransform {
140131 let ListTransformOptions :: Bound ( lambda) = options else {
141132 vortex_bail ! ( "list_transform()'s lambda is not bound" ) ;
142133 } ;
143- let list = if lambda. is_infallible ( ) {
144- Lists :: as_they_are ( args. get ( 0 ) ?, ctx) ?
145- } else {
146- Lists :: visible_elements_only ( args. get ( 0 ) ?, ctx) ?
147- } ;
148- list. with_elements ( |elements| lambda. evaluate ( vec ! [ elements. clone( ) ] , elements. len ( ) ) )
134+ match args. get ( 0 ) ?. try_downcast :: < List > ( ) {
135+ Ok ( list) => transform_list ( list, lambda, ctx) ,
136+ Err ( list) => transform_list_view ( list. execute :: < ListViewArray > ( ctx) ?, lambda, ctx) ,
137+ }
149138 }
150139
151140 fn is_strict ( & self , _options : & ListTransformOptions ) -> bool {
@@ -185,167 +174,62 @@ fn element_dtype(list: &DType, params: &[FieldName]) -> VortexResult<[DType; 1]>
185174 Ok ( [ element. as_ref ( ) . clone ( ) ] )
186175}
187176
188- /// The lists the lambda runs over: their elements buffer, and the offsets (and sizes) that put the
189- /// results back into lists of the same shape.
190- enum Lists {
191- List ( ListArray ) ,
192- View ( ListViewArray ) ,
193- }
194-
195- impl Lists {
196- /// The lists as they are. For a lambda that can't fail: it runs over the whole elements buffer,
197- /// and results for elements no valid list shows are never read.
198- fn as_they_are ( list : ArrayRef , ctx : & mut ExecutionCtx ) -> VortexResult < Self > {
199- Ok ( match list. try_downcast :: < List > ( ) {
200- Ok ( list) => Self :: List ( list) ,
201- Err ( list) => Self :: View ( list. execute :: < ListViewArray > ( ctx) ?) ,
202- } )
203- }
204-
205- /// The lists with nothing in their elements buffer but the elements of valid lists. For a lambda
206- /// that can fail, which must not fail on a value nobody sees.
207- ///
208- /// The lists keep their shape, trimmed of what comes before the first list and after the last,
209- /// unless a null list holds elements or a list view's lists aren't laid out back to back. Only
210- /// then are the elements copied.
211- fn visible_elements_only ( list : ArrayRef , ctx : & mut ExecutionCtx ) -> VortexResult < Self > {
212- let list = match list. try_downcast :: < List > ( ) {
213- Ok ( list) => match trim_list ( & list, ctx) ? {
214- Some ( trimmed) => return Ok ( Self :: List ( trimmed) ) ,
215- None => list. into_array ( ) ,
216- } ,
217- Err ( list) => list,
218- } ;
219- let view = list. execute :: < ListViewArray > ( ctx) ?;
220- match empty_null_lists ( & view, ctx) ? {
221- None if view. is_zero_copy_to_list ( ) => Ok ( Self :: View (
222- view. rebuild ( ListViewRebuildMode :: TrimElements , ctx) ?,
223- ) ) ,
224- None => Ok ( Self :: List ( list_from_list_view ( view, ctx) ?) ) ,
225- Some ( emptied) => Ok ( Self :: List ( list_from_list_view ( emptied, ctx) ?) ) ,
226- }
227- }
228-
229- /// The same lists, with `f` of their elements buffer as their elements.
230- fn with_elements (
231- self ,
232- f : impl FnOnce ( & ArrayRef ) -> VortexResult < ArrayRef > ,
233- ) -> VortexResult < ArrayRef > {
234- match self {
235- Self :: List ( list) => {
236- let values = f ( list. elements ( ) ) ?;
237- vortex_ensure_eq ! ( values. len( ) , list. elements( ) . len( ) ) ;
238- // SAFETY: the offsets and validity are the list's, over one value per element.
239- Ok ( unsafe {
240- ListArray :: new_unchecked ( values, list. offsets ( ) . clone ( ) , list. list_validity ( ) )
241- }
242- . into_array ( ) )
243- }
244- Self :: View ( view) => {
245- let values = f ( view. elements ( ) ) ?;
246- vortex_ensure_eq ! ( values. len( ) , view. elements( ) . len( ) ) ;
247- // SAFETY: the offsets, sizes and validity are the view's, over one value per
248- // element, so the views keep their bounds and their layout.
249- Ok ( unsafe {
250- ListViewArray :: new_unchecked (
251- values,
252- view. offsets ( ) . clone ( ) ,
253- view. sizes ( ) . clone ( ) ,
254- view. listview_validity ( ) ,
255- )
256- . with_zero_copy_to_list ( view. is_zero_copy_to_list ( ) )
257- }
258- . into_array ( ) )
259- }
260- }
261- }
262- }
263-
264- /// The list without what comes before its first list and after its last, or `None` when a null
265- /// list holds elements, which only a rebuild can drop.
266- fn trim_list ( list : & ListArray , ctx : & mut ExecutionCtx ) -> VortexResult < Option < ListArray > > {
267- let offsets = list. offsets ( ) . clone ( ) . execute :: < PrimitiveArray > ( ctx) ?;
268- let nulls = null_runs ( & list. list_validity ( ) , list. len ( ) , ctx) ?;
269- match_each_integer_ptype ! ( offsets. ptype( ) , |O | {
270- let offsets = offsets. as_slice:: <O >( ) ;
271- // The offsets are sorted, so a run of null lists is empty when it starts and ends at the
272- // same offset.
273- if nulls
274- . iter( )
275- . any( |& ( start, end) | offsets[ start] != offsets[ end] )
276- {
277- return Ok ( None ) ;
278- }
279- let first = offsets[ 0 ] ;
280- let ( start, end) : ( usize , usize ) = ( first. as_( ) , offsets[ offsets. len( ) - 1 ] . as_( ) ) ;
281- if start == 0 && end == list. elements( ) . len( ) {
282- return Ok ( Some ( list. clone( ) ) ) ;
283- }
284- let shifted: Buffer <O > = offsets. iter( ) . map( |& offset| offset - first) . collect( ) ;
285- // SAFETY: the elements are cut to the lists' range and the offsets shifted by its start,
286- // so every list keeps its elements.
287- Ok ( Some ( unsafe {
288- ListArray :: new_unchecked(
289- list. elements( ) . slice( start..end) ?,
290- shifted. into_array( ) ,
291- list. list_validity( ) ,
292- )
293- } ) )
294- } )
177+ /// The lambda over a list's elements, inside the list's offsets and validity.
178+ ///
179+ /// A lambda that can fail runs only on the elements valid lists reference. One that can't runs on
180+ /// every element from the first list to the last: results under null lists are never read.
181+ fn transform_list (
182+ list : ListArray ,
183+ lambda : & BoundLambda ,
184+ ctx : & mut ExecutionCtx ,
185+ ) -> VortexResult < ArrayRef > {
186+ let list = if lambda. is_infallible ( ) {
187+ trim_ends ( list, ctx) ?
188+ } else {
189+ list. rebuild_with_referenced_valid_elements ( ctx) ?
190+ } ;
191+ let values = lambda. evaluate ( vec ! [ list. elements( ) . clone( ) ] , list. elements ( ) . len ( ) ) ?;
192+ vortex_ensure_eq ! ( values. len( ) , list. elements( ) . len( ) ) ;
193+ // SAFETY: the offsets and validity are the list's, over one value per element.
194+ Ok (
195+ unsafe { ListArray :: new_unchecked ( values, list. offsets ( ) . clone ( ) , list. list_validity ( ) ) }
196+ . into_array ( ) ,
197+ )
295198}
296199
297- /// The runs of null rows, as `(start, end)` ranges.
298- fn null_runs (
299- validity : & Validity ,
300- len : usize ,
200+ /// The lambda over a list view's elements, inside the view's offsets, sizes and validity.
201+ ///
202+ /// A lambda that can fail runs only on the elements valid lists reference.
203+ fn transform_list_view (
204+ view : ListViewArray ,
205+ lambda : & BoundLambda ,
301206 ctx : & mut ExecutionCtx ,
302- ) -> VortexResult < Vec < ( usize , usize ) > > {
303- if matches ! ( validity, Validity :: NonNullable | Validity :: AllValid ) {
304- return Ok ( Vec :: new ( ) ) ;
207+ ) -> VortexResult < ArrayRef > {
208+ let view = if lambda. is_infallible ( ) {
209+ view
210+ } else {
211+ view. rebuild_with_referenced_valid_elements ( ctx) ?
212+ } ;
213+ let values = lambda. evaluate ( vec ! [ view. elements( ) . clone( ) ] , view. elements ( ) . len ( ) ) ?;
214+ vortex_ensure_eq ! ( values. len( ) , view. elements( ) . len( ) ) ;
215+ // SAFETY: the offsets, sizes and validity are the view's, over one value per element, so the
216+ // views keep their bounds and their layout.
217+ Ok ( unsafe {
218+ ListViewArray :: new_unchecked (
219+ values,
220+ view. offsets ( ) . clone ( ) ,
221+ view. sizes ( ) . clone ( ) ,
222+ view. listview_validity ( ) ,
223+ )
224+ . with_zero_copy_to_list ( view. is_zero_copy_to_list ( ) )
305225 }
306- Ok ( match ( !& validity. execute_mask ( len, ctx) ?) . slices ( ) {
307- AllOr :: All => vec ! [ ( 0 , len) ] ,
308- AllOr :: None => Vec :: new ( ) ,
309- AllOr :: Some ( runs) => runs. to_vec ( ) ,
310- } )
226+ . into_array ( ) )
311227}
312228
313- /// The lists with every null list emptied, so that nothing under a null list reaches the lambda, or
314- /// `None` when no null list holds elements.
315- fn empty_null_lists (
316- list : & ListViewArray ,
317- ctx : & mut ExecutionCtx ,
318- ) -> VortexResult < Option < ListViewArray > > {
319- let validity = list. listview_validity ( ) ;
320- let nulls = null_runs ( & validity, list. len ( ) , ctx) ?;
321- if nulls. is_empty ( ) {
322- return Ok ( None ) ;
229+ /// The list without what comes before its first list and after its last.
230+ fn trim_ends ( list : ListArray , ctx : & mut ExecutionCtx ) -> VortexResult < ListArray > {
231+ if list. offset_at ( 0 , ctx) ? == 0 && list. offset_at ( list. len ( ) , ctx) ? == list. elements ( ) . len ( ) {
232+ return Ok ( list) ;
323233 }
324- let sizes = list. sizes ( ) . clone ( ) . execute :: < PrimitiveArray > ( ctx) ?;
325- let sizes = match_each_integer_ptype ! ( sizes. ptype( ) , |T | {
326- let sizes = sizes. as_slice:: <T >( ) ;
327- // Writers usually leave null lists empty already.
328- if nulls
329- . iter( )
330- . all( |& ( start, end) | sizes[ start..end] . iter( ) . all( |size| * size == T :: default ( ) ) )
331- {
332- return Ok ( None ) ;
333- }
334- let mut sizes = BufferMut :: copy_from( sizes) ;
335- for & ( start, end) in & nulls {
336- sizes[ start..end] . fill( T :: default ( ) ) ;
337- }
338- PrimitiveArray :: new( sizes. freeze( ) , Validity :: NonNullable )
339- } ) ;
340- // SAFETY: only sizes changed, and only to 0, so every view stays within the elements. The new
341- // array is not marked zero-copy to a list: an emptied list leaves its elements behind as a gap,
342- // which the rebuild must drop.
343- Ok ( Some ( unsafe {
344- ListViewArray :: new_unchecked (
345- list. elements ( ) . clone ( ) ,
346- list. offsets ( ) . clone ( ) ,
347- sizes. into_array ( ) ,
348- validity,
349- )
350- } ) )
234+ list. reset_offsets ( false , ctx)
351235}
0 commit comments