Skip to main content

geoparquet_batch_writer_derive/
lib.rs

1use darling::{FromDeriveInput, FromField};
2use proc_macro::TokenStream;
3use quote::{format_ident, quote};
4use syn::{DeriveInput, Ident, PathArguments, Type, parse_macro_input, spanned::Spanned};
5
6#[derive(Clone, Copy, Debug)]
7enum GeometryKind {
8    Point,
9    LineString,
10    Polygon,
11    MultiPoint,
12    MultiLineString,
13    MultiPolygon,
14    Geometry,
15    GeometryCollection,
16}
17
18/// Darling struct for parsing container-level attributes
19#[derive(FromDeriveInput, Debug)]
20#[darling(attributes(geo), forward_attrs(allow, doc, cfg))]
21struct GeoParquetOpts {
22    ident: Ident,
23    data: darling::ast::Data<(), GeoFieldOpts>,
24}
25
26/// Darling struct for parsing field-level attributes
27#[derive(FromField, Debug)]
28#[darling(attributes(geo), forward_attrs(allow, doc, cfg))]
29struct GeoFieldOpts {
30    ident: Option<Ident>,
31    ty: Type,
32
33    /// Custom column name for the field
34    #[darling(default)]
35    name: Option<String>,
36
37    /// Mark field as geometry column
38    #[darling(default)]
39    geometry: bool,
40
41    /// Geometry dimension (XY, XYZ, XYM)
42    #[darling(default)]
43    dim: Option<String>,
44}
45
46#[proc_macro_derive(GeoParquetRowData, attributes(geo))]
47pub fn derive_geo_parquet_row_data(input: TokenStream) -> TokenStream {
48    let input = parse_macro_input!(input as DeriveInput);
49    match impl_geoparquet_row_data(&input) {
50        Ok(ts) => ts,
51        Err(e) => e.to_compile_error().into(),
52    }
53}
54
55#[proc_macro_derive(GeoParquetRowStruct, attributes(geo))]
56pub fn derive_geo_parquet_row_struct(input: TokenStream) -> TokenStream {
57    let input = parse_macro_input!(input as DeriveInput);
58    match impl_geoparquet_row_struct(&input) {
59        Ok(ts) => ts,
60        Err(e) => e.to_compile_error().into(),
61    }
62}
63
64fn impl_geoparquet_row_data(input: &DeriveInput) -> syn::Result<TokenStream> {
65    // Parse attributes using darling
66    let opts = GeoParquetOpts::from_derive_input(input).map_err(|e| syn::Error::from(e))?;
67
68    let struct_ident = &opts.ident;
69
70    let fields = match &opts.data {
71        darling::ast::Data::Struct(fields) => fields,
72        _ => {
73            return Err(syn::Error::new(
74                input.span(),
75                "GeoParquetRowData supports only structs",
76            ));
77        }
78    };
79
80    struct FieldInfo {
81        ident: Ident,
82        col_name: String,
83        ty: syn::Type,
84        is_option: bool,
85        is_geometry: bool,
86        geom_kind: Option<GeometryKind>,
87        dim: Option<String>, // XY, XYZ, XYM
88    }
89
90    let mut finfos: Vec<FieldInfo> = Vec::new();
91    let mut geom_count = 0usize;
92
93    for field_opts in &fields.fields {
94        let ident = field_opts.ident.clone().ok_or_else(|| {
95            syn::Error::new(
96                field_opts.ty.span(),
97                "GeoParquetRowData requires named fields",
98            )
99        })?;
100
101        // Use provided name or default to field identifier
102        let col_name = field_opts.name.clone().unwrap_or_else(|| ident.to_string());
103
104        // Check if explicitly marked as geometry
105        let is_geometry = field_opts.geometry;
106        let dim = field_opts.dim.clone();
107
108        // Detect Option<T>
109        let (is_option, inner_ty) = match option_inner(&field_opts.ty) {
110            Some(t) => (true, t),
111            None => (false, &field_opts.ty),
112        };
113
114        // Heuristic geometry detection if not annotated
115        let detected_kind = geometry_kind(inner_ty);
116        let is_geometry = is_geometry || detected_kind.is_some();
117
118        if is_geometry {
119            geom_count += 1;
120        }
121
122        finfos.push(FieldInfo {
123            ident,
124            col_name,
125            ty: inner_ty.clone(),
126            is_option,
127            is_geometry,
128            geom_kind: detected_kind,
129            dim,
130        });
131    }
132
133    if geom_count == 0 {
134        return Err(syn::Error::new(
135            input.span(),
136            "No geometry field found. Mark one with #[geo(geometry)] or use a type like geo_types::Point<f64>, LineString<f64>, Polygon<f64>, MultiPoint<f64>, MultiLineString<f64>, MultiPolygon<f64>, Geometry<f64>, or GeometryCollection<f64>.",
137        ));
138    }
139    if geom_count > 1 {
140        return Err(syn::Error::new(
141            input.span(),
142            "Multiple geometry fields found; currently only one geometry field is supported.",
143        ));
144    }
145
146    // Ensure geometry field type is recognized if explicitly marked
147    for fi in &finfos {
148        if fi.is_geometry && fi.geom_kind.is_none() {
149            return Err(syn::Error::new(
150                fi.ty.span(),
151                "Unsupported geometry type. Expected geo_types::Point<f64>, LineString<f64>, Polygon<f64>, MultiPoint<f64>, MultiLineString<f64>, MultiPolygon<f64>, Geometry<f64>, or GeometryCollection<f64>.",
152            ));
153        }
154    }
155
156    // Generate schema fields & array builders
157    let mut schema_field_tokens = Vec::new();
158    let mut array_expr_tokens = Vec::new();
159
160    // geometry shared setup (Type depends on geometry kind)
161    let geom_type_ident = format_ident!("__gp_geom_type");
162    let geom_setup_tokens = |kind: GeometryKind,
163                             dim_string: Option<&str>|
164     -> proc_macro2::TokenStream {
165        let dim_expr = match dim_string.unwrap_or("XY") {
166            "XYZ" => quote!(::geoparquet_batch_writer::__dep::geoarrow_schema::Dimension::XYZ),
167            "XYM" => quote!(::geoparquet_batch_writer::__dep::geoarrow_schema::Dimension::XYM),
168            _ => quote!(::geoparquet_batch_writer::__dep::geoarrow_schema::Dimension::XY),
169        };
170        let ty_ctor_two = match kind {
171            GeometryKind::Point => Some(quote!(
172                ::geoparquet_batch_writer::__dep::geoarrow_schema::PointType::new
173            )),
174            GeometryKind::LineString => Some(quote!(
175                ::geoparquet_batch_writer::__dep::geoarrow_schema::LineStringType::new
176            )),
177            GeometryKind::Polygon => Some(quote!(
178                ::geoparquet_batch_writer::__dep::geoarrow_schema::PolygonType::new
179            )),
180            GeometryKind::MultiPoint => Some(quote!(
181                ::geoparquet_batch_writer::__dep::geoarrow_schema::MultiPointType::new
182            )),
183            GeometryKind::MultiLineString => Some(quote!(
184                ::geoparquet_batch_writer::__dep::geoarrow_schema::MultiLineStringType::new
185            )),
186            GeometryKind::MultiPolygon => Some(quote!(
187                ::geoparquet_batch_writer::__dep::geoarrow_schema::MultiPolygonType::new
188            )),
189            GeometryKind::Geometry => None,
190            GeometryKind::GeometryCollection => None,
191        };
192        let one_arg_ctor = match kind {
193            GeometryKind::Geometry => Some(quote!(
194                ::geoparquet_batch_writer::__dep::geoarrow_schema::GeometryType::new
195            )),
196            GeometryKind::GeometryCollection => Some(quote!(
197                ::geoparquet_batch_writer::__dep::geoarrow_schema::GeometryCollectionType::new
198            )),
199            _ => None,
200        };
201
202        if let Some(ctor) = ty_ctor_two {
203            quote! {
204                let #geom_type_ident = {
205                    let dim = #dim_expr;
206                    #ctor(dim, ::std::sync::Arc::new(::std::default::Default::default()))
207                };
208            }
209        } else if let Some(ctor1) = one_arg_ctor {
210            quote! {
211                let #geom_type_ident = {
212                    #ctor1(::std::sync::Arc::new(::std::default::Default::default()))
213                };
214            }
215        } else {
216            // Fallback, should not happen
217            quote! {
218                let #geom_type_ident = {
219                    let dim = #dim_expr;
220                    ::geoparquet_batch_writer::__dep::geoarrow_schema::GeometryType::new(::std::sync::Arc::new(::std::default::Default::default()))
221                };
222            }
223        }
224    };
225
226    // Flag whether we need geometry init
227    let mut geometry_init_tokens: Option<proc_macro2::TokenStream> = None;
228
229    for (idx, fi) in finfos.iter().enumerate() {
230        if fi.is_geometry {
231            let col_name_lit = syn::LitStr::new(&fi.col_name, fi.ident.span());
232            let is_option = fi.is_option;
233            // Determine geometry kind (from detection or default to Geometry)
234            let kind = fi.geom_kind.unwrap_or(GeometryKind::Geometry);
235
236            // Schema field (nullable if Option). to_field may return Result in some versions.
237            schema_field_tokens.push(quote! {
238                #geom_type_ident.to_field(#col_name_lit, #is_option)
239            });
240
241            // Arrays: choose correct builder and push method
242            let b_ident = format_ident!("__gp_geom_builder");
243            let arr_ident = format_ident!("__gp_arr_{}", idx);
244            let ident = &fi.ident;
245
246            let (builder_path, push_method_ident) = match kind {
247                GeometryKind::Point => (
248                    quote!(::geoparquet_batch_writer::__dep::geoarrow_array::builder::PointBuilder),
249                    format_ident!("push_point"),
250                ),
251                GeometryKind::LineString => (
252                    quote!(::geoparquet_batch_writer::__dep::geoarrow_array::builder::LineStringBuilder),
253                    format_ident!("push_line_string"),
254                ),
255                GeometryKind::Polygon => (
256                    quote!(::geoparquet_batch_writer::__dep::geoarrow_array::builder::PolygonBuilder),
257                    format_ident!("push_polygon"),
258                ),
259                GeometryKind::MultiPoint => (
260                    quote!(::geoparquet_batch_writer::__dep::geoarrow_array::builder::MultiPointBuilder),
261                    format_ident!("push_multi_point"),
262                ),
263                GeometryKind::MultiLineString => (
264                    quote!(::geoparquet_batch_writer::__dep::geoarrow_array::builder::MultiLineStringBuilder),
265                    format_ident!("push_multi_line_string"),
266                ),
267                GeometryKind::MultiPolygon => (
268                    quote!(::geoparquet_batch_writer::__dep::geoarrow_array::builder::MultiPolygonBuilder),
269                    format_ident!("push_multi_polygon"),
270                ),
271                GeometryKind::Geometry => (
272                    quote!(::geoparquet_batch_writer::__dep::geoarrow_array::builder::GeometryBuilder),
273                    format_ident!("push_geometry"),
274                ),
275                GeometryKind::GeometryCollection => (
276                    quote!(::geoparquet_batch_writer::__dep::geoarrow_array::builder::GeometryCollectionBuilder),
277                    format_ident!("push_geometry_collection"),
278                ),
279            };
280
281            // Make builder pushes respect nullability and silence any unused Result from push_null
282            let push_tokens = if fi.is_option {
283                quote! {
284                    for row in rows {
285                        if let Some(g) = row.#ident.as_ref() {
286                            let _ = #b_ident.#push_method_ident(Some(g));
287                        } else {
288                            // If push_null returns a Result in this geoarrow version, ignore it explicitly
289                            let _ = #b_ident.push_null();
290                        }
291                    }
292                }
293            } else {
294                quote! {
295                    for row in rows {
296                        let g = &row.#ident;
297                        let _ = #b_ident.#push_method_ident(Some(g));
298                    }
299                }
300            };
301
302            array_expr_tokens.push(quote! {{
303                use ::geoparquet_batch_writer::__dep::geoarrow_array::GeoArrowArray as _;
304                let mut #b_ident = #builder_path::new(#geom_type_ident.clone());
305                #push_tokens
306                let #arr_ident = ::std::sync::Arc::new(#b_ident.finish().into_array_ref());
307                #arr_ident
308            }});
309
310            // Initialize the appropriate GeoArrow schema type with requested dim
311            geometry_init_tokens = Some(geom_setup_tokens(kind, fi.dim.as_deref()));
312        } else {
313            // Scalar or String
314            let dt = arrow_datatype(&fi.ty)?;
315            let (array_ty, from_tokens) = array_ctor(&fi.ty, fi.is_option)?;
316            let col_name_lit = syn::LitStr::new(&fi.col_name, fi.ident.span());
317            let is_option = fi.is_option;
318            schema_field_tokens.push(quote! {
319                ::geoparquet_batch_writer::__dep::arrow_schema::Field::new(#col_name_lit, #dt, #is_option)
320            });
321            let arr_ident = format_ident!("__gp_arr_{}", idx);
322
323            // rows.iter().map(|r| r.field) vs map(|r| r.field.as_ref()…) for Option & String
324            let map_expr = value_mapper(&fi.ty, &fi.ident, fi.is_option);
325
326            array_expr_tokens.push(quote! {{
327                let it = rows.iter().map(#map_expr);
328                let arr: ::std::sync::Arc<#array_ty> = #from_tokens;
329                let #arr_ident: ::std::sync::Arc<dyn ::geoparquet_batch_writer::__dep::arrow_array::Array> = arr;
330                #arr_ident
331            }});
332        }
333    }
334
335    let schema_vec_tokens = quote! {
336        {
337            ::std::sync::Arc::new(::geoparquet_batch_writer::__dep::arrow_schema::Schema::new(vec![
338                #(#schema_field_tokens),*
339            ]))
340        }
341    };
342
343    let arrays_vec_tokens = quote! {
344        {
345            ::std::result::Result::Ok(vec![
346                #(#array_expr_tokens),*
347            ])
348        }
349    };
350
351    let expanded = quote! {
352        impl ::geoparquet_batch_writer::GeoParquetRowData for #struct_ident
353        where Self: Send + Sync
354        {
355            fn schema() -> ::std::sync::Arc<::geoparquet_batch_writer::__dep::arrow_schema::Schema> {
356                #geometry_init_tokens
357                #schema_vec_tokens
358            }
359
360            fn to_arrays(rows: &[Self]) -> ::geoparquet_batch_writer::__dep::Result<Vec<::std::sync::Arc<dyn ::geoparquet_batch_writer::__dep::arrow_array::Array>>> {
361                #geometry_init_tokens
362                #arrays_vec_tokens
363            }
364        }
365    };
366
367    Ok(expanded.into())
368}
369
370fn impl_geoparquet_row_struct(input: &DeriveInput) -> syn::Result<TokenStream> {
371    // Parse attributes using darling
372    let opts = GeoParquetOpts::from_derive_input(input).map_err(|e| syn::Error::from(e))?;
373
374    let struct_ident = &opts.ident;
375
376    let fields = match &opts.data {
377        darling::ast::Data::Struct(fields) => fields,
378        _ => {
379            return Err(syn::Error::new(
380                input.span(),
381                "GeoParquetRowStruct supports only structs",
382            ));
383        }
384    };
385
386    struct FieldInfo {
387        ident: Ident,
388        col_name: String,
389        ty: syn::Type,
390        is_option: bool,
391    }
392
393    let mut finfos: Vec<FieldInfo> = Vec::new();
394
395    for field_opts in &fields.fields {
396        let ident = field_opts.ident.clone().ok_or_else(|| {
397            syn::Error::new(
398                field_opts.ty.span(),
399                "GeoParquetRowStruct requires named fields",
400            )
401        })?;
402
403        // Use provided name or default to field identifier
404        let col_name = field_opts.name.clone().unwrap_or_else(|| ident.to_string());
405
406        // Detect Option<T>
407        let (is_option, inner_ty) = match option_inner(&field_opts.ty) {
408            Some(t) => (true, t),
409            None => (false, &field_opts.ty),
410        };
411
412        finfos.push(FieldInfo {
413            ident,
414            col_name,
415            ty: inner_ty.clone(),
416            is_option,
417        });
418    }
419
420    // Generate struct fields for DataType::Struct
421    let mut struct_field_tokens = Vec::new();
422
423    for fi in &finfos {
424        let col_name_lit = syn::LitStr::new(&fi.col_name, fi.ident.span());
425        let ty = &fi.ty;
426        let is_option = fi.is_option;
427
428        struct_field_tokens.push(quote! {
429            ::geoparquet_batch_writer::__dep::arrow_schema::Field::new(
430                #col_name_lit,
431                <#ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::data_type(),
432                #is_option
433            )
434        });
435    }
436
437    // Generate array creation for StructArray
438    let mut field_array_tokens_iter_values = Vec::new();
439    let mut field_array_tokens_iter = Vec::new();
440
441    for (idx, fi) in finfos.iter().enumerate() {
442        let ident = &fi.ident;
443        let ty = &fi.ty;
444        let is_option = fi.is_option;
445        let arr_ident = format_ident!("__gp_arr_{}", idx);
446
447        if is_option {
448            // Field is Option<T>, extract Option<T> and use T::from_iter
449            field_array_tokens_iter_values.push(quote! {
450                let field_iter = values.iter().map(|r: &Self| r.#ident.clone());
451                let #arr_ident = <#ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::from_iter(field_iter);
452            });
453            field_array_tokens_iter.push(quote! {
454                let field_iter = values.iter().map(|r: &Self| r.#ident.clone());
455                let #arr_ident = <#ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::from_iter(field_iter);
456            });
457        } else {
458            // Field is T, extract T and use T::from_iter_values
459            field_array_tokens_iter_values.push(quote! {
460                let field_iter = values.iter().map(|r: &Self| r.#ident.clone());
461                let #arr_ident = <#ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::from_iter_values(field_iter);
462            });
463            field_array_tokens_iter.push(quote! {
464                let field_iter = values.iter().map(|r: &Self| r.#ident.clone());
465                let #arr_ident = <#ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::from_iter_values(field_iter);
466            });
467        }
468    }
469
470    let field_array_refs: Vec<_> = (0..finfos.len())
471        .map(|idx| format_ident!("__gp_arr_{}", idx))
472        .collect();
473
474    let expanded = quote! {
475        impl ::geoparquet_batch_writer::__dep::ArrowDataType for #struct_ident
476        where Self: Send + Sync + Clone + 'static
477        {
478            type Array = ::geoparquet_batch_writer::__dep::arrow_array::StructArray;
479
480            fn data_type() -> ::geoparquet_batch_writer::__dep::arrow_schema::DataType {
481                ::geoparquet_batch_writer::__dep::arrow_schema::DataType::Struct(
482                    ::geoparquet_batch_writer::__dep::arrow_schema::Fields::from(vec![
483                        #(#struct_field_tokens),*
484                    ])
485                )
486            }
487
488            fn from_iter_values<I>(iter: I) -> ::std::sync::Arc<Self::Array>
489            where
490                I: IntoIterator<Item = Self>,
491            {
492                let values: Vec<Self> = iter.into_iter().collect();
493                #(#field_array_tokens_iter_values)*
494
495                let field_arrays: Vec<::std::sync::Arc<dyn ::geoparquet_batch_writer::__dep::arrow_array::Array>> = vec![
496                    #(#field_array_refs as ::std::sync::Arc<dyn ::geoparquet_batch_writer::__dep::arrow_array::Array>),*
497                ];
498
499                let fields = match Self::data_type() {
500                    ::geoparquet_batch_writer::__dep::arrow_schema::DataType::Struct(fields) => fields,
501                    _ => unreachable!(),
502                };
503
504                ::std::sync::Arc::new(
505                    ::geoparquet_batch_writer::__dep::arrow_array::StructArray::new(
506                        fields,
507                        field_arrays,
508                        None
509                    )
510                )
511            }
512
513            fn from_iter<I>(iter: I) -> ::std::sync::Arc<Self::Array>
514            where
515                I: IntoIterator<Item = Option<Self>>,
516            {
517                let collected: Vec<_> = iter.into_iter().collect();
518                let len = collected.len();
519
520                // Create validity bitmap for the struct array
521                let mut validity = ::geoparquet_batch_writer::__dep::arrow_array::builder::BooleanBufferBuilder::new(len);
522                let values: Vec<Self> = collected.into_iter().map(|opt| {
523                    match opt {
524                        Some(val) => {
525                            validity.append(true);
526                            val
527                        },
528                        None => {
529                            validity.append(false);
530                            // We need a default value for None cases
531                            // This is a limitation - structs need default values for missing data
532                            ::std::default::Default::default()
533                        }
534                    }
535                }).collect();
536
537                let validity_buffer = validity.finish();
538                #(#field_array_tokens_iter)*
539
540                let field_arrays: Vec<::std::sync::Arc<dyn ::geoparquet_batch_writer::__dep::arrow_array::Array>> = vec![
541                    #(#field_array_refs as ::std::sync::Arc<dyn ::geoparquet_batch_writer::__dep::arrow_array::Array>),*
542                ];
543
544                let fields = match Self::data_type() {
545                    ::geoparquet_batch_writer::__dep::arrow_schema::DataType::Struct(fields) => fields,
546                    _ => unreachable!(),
547                };
548
549                ::std::sync::Arc::new(
550                    ::geoparquet_batch_writer::__dep::arrow_array::StructArray::new(
551                        fields,
552                        field_arrays,
553                        Some(validity_buffer.into())
554                    )
555                )
556            }
557        }
558    };
559
560    Ok(expanded.into())
561}
562
563fn option_inner(ty: &Type) -> Option<&Type> {
564    if let Type::Path(tp) = ty {
565        if let Some(seg) = tp.path.segments.last() {
566            if seg.ident == "Option" {
567                if let PathArguments::AngleBracketed(args) = &seg.arguments {
568                    if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
569                        return Some(inner);
570                    }
571                }
572            }
573        }
574    }
575    None
576}
577
578fn vec_inner(ty: &Type) -> Option<&Type> {
579    if let Type::Path(tp) = ty {
580        if let Some(seg) = tp.path.segments.last() {
581            if seg.ident == "Vec" {
582                if let PathArguments::AngleBracketed(args) = &seg.arguments {
583                    if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
584                        return Some(inner);
585                    }
586                }
587            }
588        }
589    }
590    None
591}
592
593fn geometry_kind(ty: &Type) -> Option<GeometryKind> {
594    // Expect something like geo_types::Point<f64>, geo_types::LineString<f64>, etc.
595    if let Type::Path(tp) = ty {
596        if let Some(seg) = tp.path.segments.last() {
597            let name = seg.ident.to_string();
598            if let PathArguments::AngleBracketed(args) = &seg.arguments {
599                if let Some(syn::GenericArgument::Type(Type::Path(inner))) = args.args.first() {
600                    if let Some(seg2) = inner.path.segments.last() {
601                        if seg2.ident == "f64" {
602                            return match name.as_str() {
603                                "Point" => Some(GeometryKind::Point),
604                                "LineString" => Some(GeometryKind::LineString),
605                                "Polygon" => Some(GeometryKind::Polygon),
606                                "MultiPoint" => Some(GeometryKind::MultiPoint),
607                                "MultiLineString" => Some(GeometryKind::MultiLineString),
608                                "MultiPolygon" => Some(GeometryKind::MultiPolygon),
609                                "Geometry" => Some(GeometryKind::Geometry),
610                                "GeometryCollection" => Some(GeometryKind::GeometryCollection),
611                                _ => None,
612                            };
613                        }
614                    }
615                }
616            }
617        }
618    }
619    None
620}
621
622fn arrow_datatype(ty: &Type) -> syn::Result<proc_macro2::TokenStream> {
623    // Check if this is a Vec<T> type
624    if let Some(inner_ty) = vec_inner(ty) {
625        let inner_dt = arrow_datatype(inner_ty)?;
626        return Ok(quote!(
627            ::geoparquet_batch_writer::__dep::arrow_schema::DataType::List(
628                ::std::sync::Arc::new(
629                    ::geoparquet_batch_writer::__dep::arrow_schema::Field::new(
630                        "item",
631                        #inner_dt,
632                        true
633                    )
634                )
635            )
636        ));
637    }
638
639    // Use the ArrowDataType trait to get the data type
640    Ok(quote!(<#ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::data_type()))
641}
642
643fn array_ctor(
644    ty: &Type,
645    is_option: bool,
646) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
647    // Check if this is a Vec<T> type
648    if let Some(inner_ty) = vec_inner(ty) {
649        let inner_type_name = type_name(inner_ty);
650
651        // First try primitive types with specific builders for better performance
652        let primitive_builder = match inner_type_name.as_str() {
653            "u64" => Some((
654                quote!(::geoparquet_batch_writer::__dep::arrow_array::builder::UInt64Builder),
655                quote!(values.append_value(*val)),
656            )),
657            "i64" => Some((
658                quote!(::geoparquet_batch_writer::__dep::arrow_array::builder::Int64Builder),
659                quote!(values.append_value(*val)),
660            )),
661            "u32" => Some((
662                quote!(::geoparquet_batch_writer::__dep::arrow_array::builder::UInt32Builder),
663                quote!(values.append_value(*val)),
664            )),
665            "i32" => Some((
666                quote!(::geoparquet_batch_writer::__dep::arrow_array::builder::Int32Builder),
667                quote!(values.append_value(*val)),
668            )),
669            "f64" => Some((
670                quote!(::geoparquet_batch_writer::__dep::arrow_array::builder::Float64Builder),
671                quote!(values.append_value(*val)),
672            )),
673            "f32" => Some((
674                quote!(::geoparquet_batch_writer::__dep::arrow_array::builder::Float32Builder),
675                quote!(values.append_value(*val)),
676            )),
677            "bool" => Some((
678                quote!(::geoparquet_batch_writer::__dep::arrow_array::builder::BooleanBuilder),
679                quote!(values.append_value(*val)),
680            )),
681            "String" => Some((
682                quote!(::geoparquet_batch_writer::__dep::arrow_array::builder::StringBuilder),
683                quote!(values.append_value(val)),
684            )),
685            _ => None,
686        };
687
688        if let Some((values_builder_type, values_append)) = primitive_builder {
689            let list_construction = if is_option {
690                quote! {
691                    {
692                        let mut builder = ::geoparquet_batch_writer::__dep::arrow_array::builder::ListBuilder::new(#values_builder_type::new());
693                        for opt_vec in it {
694                            if let Some(vec_val) = opt_vec {
695                                let values = builder.values();
696                                for val in vec_val {
697                                    #values_append;
698                                }
699                                builder.append(true);
700                            } else {
701                                builder.append(false);
702                            }
703                        }
704                        builder.finish()
705                    }
706                }
707            } else {
708                quote! {
709                    {
710                        let mut builder = ::geoparquet_batch_writer::__dep::arrow_array::builder::ListBuilder::new(#values_builder_type::new());
711                        for vec_val in it {
712                            let values = builder.values();
713                            for val in vec_val {
714                                #values_append;
715                            }
716                            builder.append(true);
717                        }
718                        builder.finish()
719                    }
720                }
721            };
722
723            return Ok((
724                quote!(::geoparquet_batch_writer::__dep::arrow_array::ListArray),
725                quote! { ::std::sync::Arc::new(#list_construction) },
726            ));
727        }
728
729        // For complex types that implement ArrowDataType, use a builder-based approach
730        let list_construction = if is_option {
731            quote! {
732                {
733                    // Collect the iterator once to avoid use-after-move issues
734                    let all_list_values: Vec<_> = it.collect();
735
736                    // Now create a simple list array by concatenating all values and tracking offsets
737                    let mut all_values = Vec::new();
738                    let mut validity = ::geoparquet_batch_writer::__dep::arrow_array::builder::BooleanBufferBuilder::new(all_list_values.len());
739
740                    for list_opt in &all_list_values {
741                        match list_opt {
742                            Some(vec_val) => {
743                                validity.append(true);
744                                all_values.extend(vec_val.iter().cloned());
745                            }
746                            None => {
747                                validity.append(false);
748                                // No values to add for null case
749                            }
750                        }
751                    }
752
753                    let values_array = <#inner_ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::from_iter_values(all_values);
754                    let validity_buffer = validity.finish();
755
756                    // Use try_new instead of new_unchecked
757                    ::geoparquet_batch_writer::__dep::arrow_array::ListArray::try_new(
758                        ::std::sync::Arc::new(::geoparquet_batch_writer::__dep::arrow_schema::Field::new(
759                            "item",
760                            <#inner_ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::data_type(),
761                            true
762                        )),
763                        ::geoparquet_batch_writer::__dep::arrow_buffer::OffsetBuffer::from_lengths(
764                            all_list_values.iter().map(|opt| {
765                                opt.as_ref().map(|v| v.len()).unwrap_or(0)
766                            })
767                        ),
768                        values_array,
769                        Some(validity_buffer.into())
770                    ).unwrap()
771                }
772            }
773        } else {
774            quote! {
775                {
776                    let all_vecs: Vec<_> = it.collect();
777                    let mut all_values = Vec::new();
778
779                    for vec_val in &all_vecs {
780                        all_values.extend(vec_val.iter().cloned());
781                    }
782
783                    let values_array = <#inner_ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::from_iter_values(all_values);
784
785                    ::geoparquet_batch_writer::__dep::arrow_array::ListArray::try_new(
786                        ::std::sync::Arc::new(::geoparquet_batch_writer::__dep::arrow_schema::Field::new(
787                            "item",
788                            <#inner_ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::data_type(),
789                            true
790                        )),
791                        ::geoparquet_batch_writer::__dep::arrow_buffer::OffsetBuffer::from_lengths(
792                            all_vecs.iter().map(|v| v.len())
793                        ),
794                        values_array,
795                        None
796                    ).unwrap()
797                }
798            }
799        };
800        return Ok((
801            quote!(::geoparquet_batch_writer::__dep::arrow_array::ListArray),
802            quote! { ::std::sync::Arc::new(#list_construction) },
803        ));
804    }
805
806    // Use the ArrowDataType trait for array construction
807    let arr_ty = quote!(<#ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::Array);
808    let from = if is_option {
809        quote!(<#ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::from_iter(it))
810    } else {
811        quote!(<#ty as ::geoparquet_batch_writer::__dep::ArrowDataType>::from_iter_values(it))
812    };
813    Ok((arr_ty, from))
814}
815
816fn value_mapper(ty: &Type, ident: &Ident, is_option: bool) -> proc_macro2::TokenStream {
817    // Check if this is a Vec<T> type
818    if vec_inner(ty).is_some() {
819        if is_option {
820            quote!(|r: &Self| r.#ident.as_ref())
821        } else {
822            quote!(|r: &Self| &r.#ident)
823        }
824    } else {
825        let t = type_name(ty);
826        if t == "String" {
827            if is_option {
828                quote!(|r: &Self| r.#ident.as_ref().map(|s| s.clone()))
829            } else {
830                quote!(|r: &Self| r.#ident.clone())
831            }
832        } else if is_copy_scalar(&t) {
833            if is_option {
834                quote!(|r: &Self| r.#ident)
835            } else {
836                quote!(|r: &Self| r.#ident)
837            }
838        } else {
839            // Shouldn't happen given our supported types
840            quote!(|r: &Self| r.#ident)
841        }
842    }
843}
844
845fn type_name(ty: &Type) -> String {
846    if let Type::Path(tp) = ty {
847        if let Some(seg) = tp.path.segments.last() {
848            return seg.ident.to_string();
849        }
850    }
851    format!("{}", quote!(#ty))
852}
853
854fn is_copy_scalar(name: &str) -> bool {
855    matches!(name, "u64" | "i64" | "u32" | "i32" | "f64" | "f32" | "bool")
856}