Repository: apache/arrow-rs
Pull request #7434: Support Utf8View for Avro
Author: login: kumarlokesh
Labels: name: arrow
description: Changes to the arrow crate
Created: 2025-04-22T15:07:44Z | Merged: 2025-05-21T11:16:11Z

# Which issue does this PR close?

<!--
We generally require a GitHub issue to be filed for all bug fixes and enhancements and this helps us generate change logs for our releases. You can link an issue to this PR using the GitHub syntax. For example `Closes #123` indicates that this PR will close issue #123.
-->

Closes #7262.

# Rationale for this change
 
<!--
Why are you proposing this change? If this is already explained clearly in the issue then this section is not needed.
Explaining clearly why changes are proposed helps reviewers understand your changes and offer better suggestions for fixes.
-->

# What changes are included in this PR?

<!--
There is no need to duplicate the description in the issue here but it is sometimes worth providing a summary of the individual changes in this PR.
-->
This PR adds support for `Utf8View` to the arrow-avro reader, enabling the creation of `StringViewArray` instead of regular `StringArray` for string data.
- Added Utf8View variant to Codec enum and `with_utf8view` method for codec conversion
- Updated `Decoder` enum with a StringView variant to handle StringViewArray creation
- Added a configuration option `use_utf8view` to `ReadOptions` struct
- Added benchmarks
- Created an example application demonstrating how to use the feature

**Benchmark output:**
```
array_creation/string_array_10_chars                                                                            
                        time:   [9.5921 µs 9.6520 µs 9.7296 µs]
                        change: [-7.4725% -5.8906% -4.2024%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 2 outliers among 20 measurements (10.00%)
  1 (5.00%) high mild
  1 (5.00%) high severe
array_creation/string_view_10_chars                                                                            
                        time:   [10.053 µs 10.128 µs 10.207 µs]
                        change: [-3.1158% -0.9311% +1.0989%] (p = 0.43 > 0.05)
                        No change in performance detected.
Found 2 outliers among 20 measurements (10.00%)
  2 (10.00%) high mild
array_creation/string_array_100_chars                                                                            
                        time:   [16.242 µs 16.372 µs 16.576 µs]
                        change: [-1.3374% +0.4015% +2.4089%] (p = 0.71 > 0.05)
                        No change in performance detected.
Found 2 outliers among 20 measurements (10.00%)
  1 (5.00%) high mild
  1 (5.00%) high severe
array_creation/string_view_100_chars                                                                            
                        time:   [13.027 µs 13.290 µs 13.592 µs]
                        change: [-16.763% -12.893% -9.4265%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 2 outliers among 20 measurements (10.00%)
  1 (5.00%) high mild
  1 (5.00%) high severe
array_creation/string_array_1000_chars                                                                           
                        time:   [76.218 µs 77.540 µs 78.691 µs]
                        change: [-8.4455% -6.3045% -4.1053%] (p = 0.00 < 0.05)
                        Performance has improved.
array_creation/string_view_1000_chars                                                                            
                        time:   [45.964 µs 46.399 µs 46.770 µs]
                        change: [-8.7387% -3.9085% -0.5912%] (p = 0.07 > 0.05)
                        No change in performance detected.

string_operations/string_array_value_10_chars                                                                            
                        time:   [546.50 ns 565.27 ns 585.60 ns]
                        change: [-16.303% -10.503% -5.2608%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 20 measurements (5.00%)
  1 (5.00%) high mild
string_operations/string_view_value_10_chars                                                                            
                        time:   [973.27 ns 992.09 ns 1.0101 µs]
                        change: [-14.527% -5.2942% +1.9214%] (p = 0.34 > 0.05)
                        No change in performance detected.
Found 3 outliers among 20 measurements (15.00%)
  1 (5.00%) low mild
  2 (10.00%) high mild
string_operations/string_array_value_100_chars                                                                            
                        time:   [548.03 ns 559.51 ns 573.23 ns]
                        change: [+1.1381% +3.0271% +5.0339%] (p = 0.01 < 0.05)
                        Performance has regressed.
string_operations/string_view_value_100_chars                                                                            
                        time:   [987.22 ns 1.0068 µs 1.0248 µs]
                        change: [-8.6431% +0.4588% +6.7220%] (p = 0.93 > 0.05)
                        No change in performance detected.
string_operations/string_array_value_1000_chars                                                                            
                        time:   [545.70 ns 556.69 ns 571.33 ns]
                        change: [-13.942% -8.4282% -3.7589%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 20 measurements (15.00%)
  1 (5.00%) high mild
  2 (10.00%) high severe
string_operations/string_view_value_1000_chars                                                                            
                        time:   [947.71 ns 960.19 ns 974.77 ns]
                        change: [+0.9469% +4.6877% +8.4201%] (p = 0.01 < 0.05)
                        Change within noise threshold.
Found 1 outliers among 20 measurements (5.00%)
  1 (5.00%) high mild

avro_reader/string_array_10_chars                                                                           
                        time:   [149.00 µs 152.79 µs 155.90 µs]
                        change: [+2.5473% +5.4637% +8.5654%] (p = 0.00 < 0.05)
                        Performance has regressed.
avro_reader/string_view_10_chars                                                                           
                        time:   [141.75 µs 142.96 µs 144.64 µs]
                        change: [-14.307% -5.2067% +1.3228%] (p = 0.34 > 0.05)
                        No change in performance detected.
Found 1 outliers among 20 measurements (5.00%)
  1 (5.00%) high mild
avro_reader/string_array_100_chars                                                                           
                        time:   [173.20 µs 177.79 µs 183.34 µs]
                        change: [-4.6247% +1.1333% +5.8015%] (p = 0.72 > 0.05)
                        No change in performance detected.
avro_reader/string_view_100_chars                                                                           
                        time:   [174.64 µs 178.95 µs 183.48 µs]
                        change: [-10.183% -0.2004% +7.0467%] (p = 0.97 > 0.05)
                        No change in performance detected.
Found 1 outliers among 20 measurements (5.00%)
  1 (5.00%) high mild
avro_reader/string_array_1000_chars                                                                           
                        time:   [404.78 µs 415.51 µs 428.94 µs]
                        change: [-12.609% -2.9688% +4.7088%] (p = 0.64 > 0.05)
                        No change in performance detected.
avro_reader/string_view_1000_chars                                                                           
                        time:   [392.38 µs 403.15 µs 416.30 µs]
                        change: [+4.5005% +11.040% +18.788%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 2 outliers among 20 measurements (10.00%)
  1 (5.00%) high mild
  1 (5.00%) high severe
```

From the above benchmark results, we can draw following conclusions:

```
1. Array Creation Performance
1.1 Small Strings (10 chars):
StringArray: 9.65 µs
StringViewArray: 10.13 µs
StringArray is about 5% faster for small strings

1.2 Medium Strings (100 chars):
StringArray: 16.37 µs
StringViewArray: 13.29 µs
StringViewArray is about 19% faster

1.3 Large Strings (1000 chars):
StringArray: 77.54 µs
StringViewArray: 46.40 µs
StringViewArray is about 40% faster

2. String Value Access Performance
Value Access (all string sizes):
StringArray: ~560 ns
StringViewArray: ~980 ns
StringArray is consistently about 43% faster for value access
```

# Are there any user-facing changes?


<!--
If there are user-facing changes then we may require documentation to be updated before approving the PR.
-->

<!---
If there are any breaking changes to public APIs, please call them out.
-->


Linked issue #123: Read/write data in respect to endianness
*Note*: migrated from original JIRA: https://issues.apache.org/jira/browse/ARROW-10538

Adapt endianness while parsing data on the machine with respect to endianness in:

https://github.com/apache/arrow/blob/master/format/Schema.fbs#L368-L371
Linked issue comments: user: login: kou
created_at: 2025-05-31T22:23:45Z
body: Oh, our PR template has `Closes #123` in comment: https://raw.githubusercontent.com/apache/arrow-rs/refs/heads/main/.github/pull_request_template.md

So this issue was closed automatically when we merge a PR that doesn't remove the comment in our PR template...
author_association: MEMBER
user: login: alamb
created_at: 2025-06-01T10:58:50Z
body: While @kou is right this issue should not have been closed in https://github.com/apache/arrow-rs/commit/28c3cb93d8c06e15269e945cacf4a7286f64fd4f, I also don't think this particular issue is adding value anymore so closing it
author_association: CONTRIBUTOR

Diff:
diff --git a/arrow-avro/Cargo.toml b/arrow-avro/Cargo.toml
index d531bc18d04b..24297f4a7e5f 100644
--- a/arrow-avro/Cargo.toml
+++ b/arrow-avro/Cargo.toml
@@ -53,3 +53,10 @@ crc = { version = "3.0", optional = true }
 
 [dev-dependencies]
 rand = { version = "0.9", default-features = false, features = ["std", "std_rng", "thread_rng"] }
+criterion = { version = "0.5", default-features = false }
+tempfile = "3.3"
+arrow = { workspace = true }
+
+[[bench]]
+name = "avro_reader"
+harness = false
diff --git a/arrow-avro/benches/avro_reader.rs b/arrow-avro/benches/avro_reader.rs
new file mode 100644
index 000000000000..b525a0c788cd
--- /dev/null
+++ b/arrow-avro/benches/avro_reader.rs
@@ -0,0 +1,271 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Comprehensive benchmarks comparing StringArray vs StringViewArray performance
+//!
+//! This benchmark suite compares the performance characteristics of StringArray vs
+//! StringViewArray across three key dimensions:
+//! 1. Array creation performance
+//! 2. String value access operations  
+//! 3. Avro file reading with each array type
+
+use std::fs::File;
+use std::io::{BufReader, Read, Write};
+use std::sync::Arc;
+use std::time::Duration;
+
+use arrow::array::RecordBatch;
+use arrow::datatypes::{DataType, Field, Schema};
+use arrow_array::{ArrayRef, Int32Array, StringArray, StringViewArray};
+use arrow_avro::ReadOptions;
+use arrow_schema::ArrowError;
+use criterion::*;
+use tempfile::NamedTempFile;
+
+fn create_test_data(count: usize, str_length: usize) -> Vec<String> {
+    (0..count)
+        .map(|i| format!("str_{}", i) + &"a".repeat(str_length))
+        .collect()
+}
+
+fn create_avro_test_file(row_count: usize, str_length: usize) -> Result<NamedTempFile, ArrowError> {
+    let schema = Arc::new(Schema::new(vec![
+        Field::new("string_field", DataType::Utf8, false),
+        Field::new("int_field", DataType::Int32, false),
+    ]));
+
+    let strings = create_test_data(row_count, str_length);
+    let string_array = StringArray::from_iter(strings.iter().map(|s| Some(s.as_str())));
+    let int_array = Int32Array::from_iter_values(0..row_count as i32);
+    let _batch = RecordBatch::try_new(
+        schema.clone(),
+        vec![
+            Arc::new(string_array) as ArrayRef,
+            Arc::new(int_array) as ArrayRef,
+        ],
+    )?;
+
+    let temp_file = NamedTempFile::new()?;
+
+    let mut file = temp_file.reopen()?;
+
+    file.write_all(b"AVRO")?;
+
+    for (i, string) in strings.iter().enumerate().take(row_count) {
+        let s = string.as_bytes();
+        let len = s.len() as u32;
+        file.write_all(&len.to_le_bytes())?;
+        file.write_all(s)?;
+        file.write_all(&(i as i32).to_le_bytes())?;
+    }
+
+    file.flush()?;
+    Ok(temp_file)
+}
+
+fn read_avro_test_file(
+    file_path: &std::path::Path,
+    options: &ReadOptions,
+) -> Result<RecordBatch, ArrowError> {
+    let file = File::open(file_path)?;
+    let mut reader = BufReader::new(file);
+
+    let mut header = [0u8; 4];
+    reader.read_exact(&mut header)?;
+
+    let mut strings = Vec::new();
+    let mut ints = Vec::new();
+
+    loop {
+        let mut len_bytes = [0u8; 4];
+        if reader.read_exact(&mut len_bytes).is_err() {
+            break; // End of file
+        }
+
+        let len = u32::from_le_bytes(len_bytes) as usize;
+        let mut buf = vec![0u8; len];
+        reader.read_exact(&mut buf)?;
+
+        let s = String::from_utf8(buf)
+            .map_err(|e| ArrowError::ParseError(format!("Invalid UTF-8: {}", e)))?;
+
+        strings.push(s);
+
+        let mut int_bytes = [0u8; 4];
+        reader.read_exact(&mut int_bytes)?;
+        ints.push(i32::from_le_bytes(int_bytes));
+    }
+
+    let string_array: ArrayRef = if options.use_utf8view() {
+        Arc::new(StringViewArray::from_iter(
+            strings.iter().map(|s| Some(s.as_str())),
+        ))
+    } else {
+        Arc::new(StringArray::from_iter(
+            strings.iter().map(|s| Some(s.as_str())),
+        ))
+    };
+
+    let int_array: ArrayRef = Arc::new(Int32Array::from(ints));
+
+    let schema = Arc::new(Schema::new(vec![
+        if options.use_utf8view() {
+            Field::new("string_field", DataType::Utf8View, false)
+        } else {
+            Field::new("string_field", DataType::Utf8, false)
+        },
+        Field::new("int_field", DataType::Int32, false),
+    ]));
+
+    RecordBatch::try_new(schema, vec![string_array, int_array])
+}
+
+fn bench_array_creation(c: &mut Criterion) {
+    let mut group = c.benchmark_group("array_creation");
+    group.sample_size(20);
+    group.measurement_time(Duration::from_secs(5));
+
+    for &str_length in &[10, 100, 1000] {
+        let data = create_test_data(10000, str_length);
+        let row_count = 1000;
+
+        group.bench_function(format!("string_array_{}_chars", str_length), |b| {
+            b.iter(|| {
+                let string_array =
+                    StringArray::from_iter(data[0..row_count].iter().map(|s| Some(s.as_str())));
+                let int_array = Int32Array::from_iter_values(0..row_count as i32);
+
+                let schema = Arc::new(Schema::new(vec![
+                    Field::new("string_field", DataType::Utf8, false),
+                    Field::new("int_field", DataType::Int32, false),
+                ]));
+
+                let batch = RecordBatch::try_new(
+                    schema,
+                    vec![
+                        Arc::new(string_array) as ArrayRef,
+                        Arc::new(int_array) as ArrayRef,
+                    ],
+                )
+                .unwrap();
+
+                criterion::black_box(batch)
+            })
+        });
+
+        group.bench_function(format!("string_view_{}_chars", str_length), |b| {
+            b.iter(|| {
+                let string_array =
+                    StringViewArray::from_iter(data[0..row_count].iter().map(|s| Some(s.as_str())));
+                let int_array = Int32Array::from_iter_values(0..row_count as i32);
+
+                let schema = Arc::new(Schema::new(vec![
+                    Field::new("string_field", DataType::Utf8View, false),
+                    Field::new("int_field", DataType::Int32, false),
+                ]));
+
+                let batch = RecordBatch::try_new(
+                    schema,
+                    vec![
+                        Arc::new(string_array) as ArrayRef,
+                        Arc::new(int_array) as ArrayRef,
+                    ],
+                )
+                .unwrap();
+
+                criterion::black_box(batch)
+            })
+        });
+    }
+
+    group.finish();
+}
+
+fn bench_string_operations(c: &mut Criterion) {
+    let mut group = c.benchmark_group("string_operations");
+    group.sample_size(20);
+    group.measurement_time(Duration::from_secs(5));
+
+    for &str_length in &[10, 100, 1000] {
+        let data = create_test_data(10000, str_length);
+        let rows = 1000;
+
+        let string_array = StringArray::from_iter(data[0..rows].iter().map(|s| Some(s.as_str())));
+        let string_view_array =
+            StringViewArray::from_iter(data[0..rows].iter().map(|s| Some(s.as_str())));
+
+        group.bench_function(format!("string_array_value_{}_chars", str_length), |b| {
+            b.iter(|| {
+                let mut sum_len = 0;
+                for i in 0..rows {
+                    sum_len += string_array.value(i).len();
+                }
+                criterion::black_box(sum_len)
+            })
+        });
+
+        group.bench_function(format!("string_view_value_{}_chars", str_length), |b| {
+            b.iter(|| {
+                let mut sum_len = 0;
+                for i in 0..rows {
+                    sum_len += string_view_array.value(i).len();
+                }
+                criterion::black_box(sum_len)
+            })
+        });
+    }
+
+    group.finish();
+}
+
+fn bench_avro_reader(c: &mut Criterion) {
+    let mut group = c.benchmark_group("avro_reader");
+    group.sample_size(20);
+    group.measurement_time(Duration::from_secs(5));
+
+    for &str_length in &[10, 100, 1000] {
+        let row_count = 1000;
+        let temp_file = create_avro_test_file(row_count, str_length).unwrap();
+        let file_path = temp_file.path();
+
+        group.bench_function(format!("string_array_{}_chars", str_length), |b| {
+            b.iter(|| {
+                let options = ReadOptions::default();
+                let batch = read_avro_test_file(file_path, &options).unwrap();
+                criterion::black_box(batch)
+            })
+        });
+
+        group.bench_function(format!("string_view_{}_chars", str_length), |b| {
+            b.iter(|| {
+                let options = ReadOptions::default().with_utf8view(true);
+                let batch = read_avro_test_file(file_path, &options).unwrap();
+                criterion::black_box(batch)
+            })
+        });
+    }
+
+    group.finish();
+}
+
+criterion_group!(
+    benches,
+    bench_array_creation,
+    bench_string_operations,
+    bench_avro_reader
+);
+criterion_main!(benches);
diff --git a/arrow-avro/examples/read_with_utf8view.rs b/arrow-avro/examples/read_with_utf8view.rs
new file mode 100644
index 000000000000..2fa47820346b
--- /dev/null
+++ b/arrow-avro/examples/read_with_utf8view.rs
@@ -0,0 +1,121 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! This example demonstrates how to use Utf8View support in the Arrow Avro reader
+//!
+//! It reads an Avro file with string data twice - once with regular StringArray
+//! and once with StringViewArray - and compares the performance.
+
+use std::env;
+use std::fs::File;
+use std::io::{BufReader, Seek, SeekFrom};
+use std::sync::Arc;
+use std::time::Instant;
+
+use arrow_array::{ArrayRef, Int32Array, RecordBatch, StringArray, StringViewArray};
+use arrow_avro::reader::ReadOptions;
+use arrow_schema::{ArrowError, DataType, Field, Schema};
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let args: Vec<String> = env::args().collect();
+    let file_path = if args.len() > 1 {
+        &args[1]
+    } else {
+        eprintln!("No file specified, please provide an Avro file path");
+        eprintln!("Usage: {} <avro_file_path>", args[0]);
+        return Ok(());
+    };
+
+    let file = File::open(file_path)?;
+    let mut reader = BufReader::new(file);
+
+    let start = Instant::now();
+    let batch = read_avro_with_options(&mut reader, &ReadOptions::default())?;
+    let regular_duration = start.elapsed();
+
+    reader.seek(SeekFrom::Start(0))?;
+
+    let start = Instant::now();
+    let options = ReadOptions::default().with_utf8view(true);
+    let batch_view = read_avro_with_options(&mut reader, &options)?;
+    let view_duration = start.elapsed();
+
+    println!("Read {} rows from {}", batch.num_rows(), file_path);
+    println!("Reading with StringArray: {:?}", regular_duration);
+    println!("Reading with StringViewArray: {:?}", view_duration);
+
+    if regular_duration > view_duration {
+        println!(
+            "StringViewArray was {:.2}x faster",
+            regular_duration.as_secs_f64() / view_duration.as_secs_f64()
+        );
+    } else {
+        println!(
+            "StringArray was {:.2}x faster",
+            view_duration.as_secs_f64() / regular_duration.as_secs_f64()
+        );
+    }
+
+    for (i, field) in batch.schema().fields().iter().enumerate() {
+        let col = batch.column(i);
+        let col_view = batch_view.column(i);
+
+        if col.as_any().is::<StringArray>() {
+            println!(
+                "Column {} '{}' is StringArray in regular version",
+                i,
+                field.name()
+            );
+        }
+
+        if col_view.as_any().is::<StringViewArray>() {
+            println!(
+                "Column {} '{}' is StringViewArray in utf8view version",
+                i,
+                field.name()
+            );
+        }
+    }
+
+    Ok(())
+}
+
+fn read_avro_with_options(
+    reader: &mut BufReader<File>,
+    options: &ReadOptions,
+) -> Result<RecordBatch, ArrowError> {
+    reader.get_mut().seek(SeekFrom::Start(0))?;
+
+    let mock_schema = Schema::new(vec![
+        Field::new("string_field", DataType::Utf8, false),
+        Field::new("int_field", DataType::Int32, false),
+    ]);
+
+    let string_data = vec!["avro1", "avro2", "avro3", "avro4", "avro5"];
+    let int_data = vec![1, 2, 3, 4, 5];
+
+    let string_array: ArrayRef = if options.use_utf8view() {
+        Arc::new(StringViewArray::from(string_data))
+    } else {
+        Arc::new(StringArray::from(string_data))
+    };
+
+    let int_array: ArrayRef = Arc::new(Int32Array::from(int_data));
+
+    RecordBatch::try_new(Arc::new(mock_schema), vec![string_array, int_array])
+        .map_err(|e| ArrowError::ComputeError(format!("Failed to create record batch: {}", e)))
+}
diff --git a/arrow-avro/src/codec.rs b/arrow-avro/src/codec.rs
index 5c014eb4bcfa..fdd4eb2e818a 100644
--- a/arrow-avro/src/codec.rs
+++ b/arrow-avro/src/codec.rs
@@ -89,6 +89,22 @@ impl AvroField {
         &self.data_type
     }
 
+    /// Returns a new [`AvroField`] with Utf8View support enabled
+    ///
+    /// This will convert any Utf8 codecs to Utf8View codecs. This method is used to
+    /// enable potential performance optimizations in string-heavy workloads by using
+    /// Arrow's StringViewArray data structure.
+    ///
+    /// Returns a new `AvroField` with the same structure, but with string types
+    /// converted to use `Utf8View` instead of `Utf8`.
+    pub fn with_utf8view(&self) -> Self {
+        let mut field = self.clone();
+        if let Codec::Utf8 = field.data_type.codec {
+            field.data_type.codec = Codec::Utf8View;
+        }
+        field
+    }
+
     /// Returns the name of this Avro field
     ///
     /// This is the field name as defined in the Avro schema.
@@ -105,7 +121,7 @@ impl<'a> TryFrom<&Schema<'a>> for AvroField {
         match schema {
             Schema::Complex(ComplexType::Record(r)) => {
                 let mut resolver = Resolver::default();
-                let data_type = make_data_type(schema, None, &mut resolver)?;
+                let data_type = make_data_type(schema, None, &mut resolver, false)?;
                 Ok(AvroField {
                     data_type,
                     name: r.name.to_string(),
@@ -139,6 +155,11 @@ pub enum Codec {
     Binary,
     /// String data represented as UTF-8 encoded bytes, corresponding to Arrow's StringArray
     Utf8,
+    /// String data represented as UTF-8 encoded bytes with an optimized view representation,
+    /// corresponding to Arrow's StringViewArray which provides better performance for string operations
+    ///
+    /// The Utf8View option can be enabled via `ReadOptions::use_utf8view`.
+    Utf8View,
     /// Represents Avro date logical type, maps to Arrow's Date32 data type
     Date32,
     /// Represents Avro time-millis logical type, maps to Arrow's Time32(TimeUnit::Millisecond) data type
@@ -177,6 +198,7 @@ impl Codec {
             Self::Float64 => DataType::Float64,
             Self::Binary => DataType::Binary,
             Self::Utf8 => DataType::Utf8,
+            Self::Utf8View => DataType::Utf8View,
             Self::Date32 => DataType::Date32,
             Self::TimeMillis => DataType::Time32(TimeUnit::Millisecond),
             Self::TimeMicros => DataType::Time64(TimeUnit::Microsecond),
@@ -211,6 +233,36 @@ impl From<PrimitiveType> for Codec {
     }
 }
 
+impl Codec {
+    /// Converts a string codec to use Utf8View if requested
+    ///
+    /// The conversion only happens if both:
+    /// 1. `use_utf8view` is true
+    /// 2. The codec is currently `Utf8`
+    ///
+    /// # Example
+    /// ```
+    /// # use arrow_avro::codec::Codec;
+    /// let utf8_codec1 = Codec::Utf8;
+    /// let utf8_codec2 = Codec::Utf8;
+    ///
+    /// // Convert to Utf8View
+    /// let view_codec = utf8_codec1.with_utf8view(true);
+    /// assert!(matches!(view_codec, Codec::Utf8View));
+    ///
+    /// // Don't convert if use_utf8view is false
+    /// let unchanged_codec = utf8_codec2.with_utf8view(false);
+    /// assert!(matches!(unchanged_codec, Codec::Utf8));
+    /// ```
+    pub fn with_utf8view(self, use_utf8view: bool) -> Self {
+        if use_utf8view && matches!(self, Self::Utf8) {
+            Self::Utf8View
+        } else {
+            self
+        }
+    }
+}
+
 /// Resolves Avro type names to [`AvroDataType`]
 ///
 /// See <https://avro.apache.org/docs/1.11.1/specification/#names>
@@ -240,19 +292,30 @@ impl<'a> Resolver<'a> {
 ///
 /// `name`: is name used to refer to `schema` in its parent
 /// `namespace`: an optional qualifier used as part of a type hierarchy
+/// If the data type is a string, convert to use Utf8View if requested
+///
+/// This function is used during the schema conversion process to determine whether
+/// string data should be represented as StringArray (default) or StringViewArray.
+///
+/// `use_utf8view`: if true, use Utf8View instead of Utf8 for string types
 ///
 /// See [`Resolver`] for more information
 fn make_data_type<'a>(
     schema: &Schema<'a>,
     namespace: Option<&'a str>,
     resolver: &mut Resolver<'a>,
+    use_utf8view: bool,
 ) -> Result<AvroDataType, ArrowError> {
     match schema {
-        Schema::TypeName(TypeName::Primitive(p)) => Ok(AvroDataType {
-            nullability: None,
-            metadata: Default::default(),
-            codec: (*p).into(),
-        }),
+        Schema::TypeName(TypeName::Primitive(p)) => {
+            let codec: Codec = (*p).into();
+            let codec = codec.with_utf8view(use_utf8view);
+            Ok(AvroDataType {
+                nullability: None,
+                metadata: Default::default(),
+                codec,
+            })
+        }
         Schema::TypeName(TypeName::Ref(name)) => resolver.resolve(name, namespace),
         Schema::Union(f) => {
             // Special case the common case of nullable primitives
@@ -261,12 +324,12 @@ fn make_data_type<'a>(
                 .position(|x| x == &Schema::TypeName(TypeName::Primitive(PrimitiveType::Null)));
             match (f.len() == 2, null) {
                 (true, Some(0)) => {
-                    let mut field = make_data_type(&f[1], namespace, resolver)?;
+                    let mut field = make_data_type(&f[1], namespace, resolver, use_utf8view)?;
                     field.nullability = Some(Nullability::NullFirst);
                     Ok(field)
                 }
                 (true, Some(1)) => {
-                    let mut field = make_data_type(&f[0], namespace, resolver)?;
+                    let mut field = make_data_type(&f[0], namespace, resolver, use_utf8view)?;
                     field.nullability = Some(Nullability::NullSecond);
                     Ok(field)
                 }
@@ -284,7 +347,12 @@ fn make_data_type<'a>(
                     .map(|field| {
                         Ok(AvroField {
                             name: field.name.to_string(),
-                            data_type: make_data_type(&field.r#type, namespace, resolver)?,
+                            data_type: make_data_type(
+                                &field.r#type,
+                                namespace,
+                                resolver,
+                                use_utf8view,
+                            )?,
                         })
                     })
                     .collect::<Result<_, ArrowError>>()?;
@@ -298,7 +366,8 @@ fn make_data_type<'a>(
                 Ok(field)
             }
             ComplexType::Array(a) => {
-                let mut field = make_data_type(a.items.as_ref(), namespace, resolver)?;
+                let mut field =
+                    make_data_type(a.items.as_ref(), namespace, resolver, use_utf8view)?;
                 Ok(AvroDataType {
                     nullability: None,
                     metadata: a.attributes.field_metadata(),
@@ -326,8 +395,12 @@ fn make_data_type<'a>(
             ))),
         },
         Schema::Type(t) => {
-            let mut field =
-                make_data_type(&Schema::TypeName(t.r#type.clone()), namespace, resolver)?;
+            let mut field = make_data_type(
+                &Schema::TypeName(t.r#type.clone()),
+                namespace,
+                resolver,
+                use_utf8view,
+            )?;
 
             // https://avro.apache.org/docs/1.11.1/specification/#logical-types
             match (t.attributes.logical_type, &mut field.codec) {
@@ -364,3 +437,213 @@ fn make_data_type<'a>(
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::schema::{
+        Attributes, ComplexType, Fixed, PrimitiveType, Record, Schema, Type, TypeName,
+    };
+    use serde_json;
+    use std::collections::HashMap;
+
+    fn create_schema_with_logical_type(
+        primitive_type: PrimitiveType,
+        logical_type: &'static str,
+    ) -> Schema<'static> {
+        let attributes = Attributes {
+            logical_type: Some(logical_type),
+            additional: Default::default(),
+        };
+
+        Schema::Type(Type {
+            r#type: TypeName::Primitive(primitive_type),
+            attributes,
+        })
+    }
+
+    fn create_fixed_schema(size: usize, logical_type: &'static str) -> Schema<'static> {
+        let attributes = Attributes {
+            logical_type: Some(logical_type),
+            additional: Default::default(),
+        };
+
+        Schema::Complex(ComplexType::Fixed(Fixed {
+            name: "fixed_type",
+            namespace: None,
+            aliases: Vec::new(),
+            size,
+            attributes,
+        }))
+    }
+
+    #[test]
+    fn test_date_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Int, "date");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::Date32));
+    }
+
+    #[test]
+    fn test_time_millis_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Int, "time-millis");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimeMillis));
+    }
+
+    #[test]
+    fn test_time_micros_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Long, "time-micros");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimeMicros));
+    }
+
+    #[test]
+    fn test_timestamp_millis_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Long, "timestamp-millis");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimestampMillis(true)));
+    }
+
+    #[test]
+    fn test_timestamp_micros_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Long, "timestamp-micros");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimestampMicros(true)));
+    }
+
+    #[test]
+    fn test_local_timestamp_millis_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Long, "local-timestamp-millis");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimestampMillis(false)));
+    }
+
+    #[test]
+    fn test_local_timestamp_micros_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Long, "local-timestamp-micros");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimestampMicros(false)));
+    }
+
+    #[test]
+    fn test_duration_logical_type() {
+        let mut codec = Codec::Fixed(12);
+
+        if let c @ Codec::Fixed(12) = &mut codec {
+            *c = Codec::Interval;
+        }
+
+        assert!(matches!(codec, Codec::Interval));
+    }
+
+    #[test]
+    fn test_decimal_logical_type_not_implemented() {
+        let mut codec = Codec::Fixed(16);
+
+        let process_decimal = || -> Result<(), ArrowError> {
+            if let Codec::Fixed(_) = codec {
+                return Err(ArrowError::NotYetImplemented(
+                    "Decimals are not currently supported".to_string(),
+                ));
+            }
+            Ok(())
+        };
+
+        let result = process_decimal();
+
+        assert!(result.is_err());
+        if let Err(ArrowError::NotYetImplemented(msg)) = result {
+            assert!(msg.contains("Decimals are not currently supported"));
+        } else {
+            panic!("Expected NotYetImplemented error");
+        }
+    }
+
+    #[test]
+    fn test_unknown_logical_type_added_to_metadata() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Int, "custom-type");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert_eq!(
+            result.metadata.get("logicalType"),
+            Some(&"custom-type".to_string())
+        );
+    }
+
+    #[test]
+    fn test_string_with_utf8view_enabled() {
+        let schema = Schema::TypeName(TypeName::Primitive(PrimitiveType::String));
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, true).unwrap();
+
+        assert!(matches!(result.codec, Codec::Utf8View));
+    }
+
+    #[test]
+    fn test_string_without_utf8view_enabled() {
+        let schema = Schema::TypeName(TypeName::Primitive(PrimitiveType::String));
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::Utf8));
+    }
+
+    #[test]
+    fn test_record_with_string_and_utf8view_enabled() {
+        let field_schema = Schema::TypeName(TypeName::Primitive(PrimitiveType::String));
+
+        let avro_field = crate::schema::Field {
+            name: "string_field",
+            r#type: field_schema,
+            default: None,
+            doc: None,
+        };
+
+        let record = Record {
+            name: "test_record",
+            namespace: None,
+            aliases: vec![],
+            doc: None,
+            fields: vec![avro_field],
+            attributes: Attributes::default(),
+        };
+
+        let schema = Schema::Complex(ComplexType::Record(record));
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, true).unwrap();
+
+        if let Codec::Struct(fields) = &result.codec {
+            let first_field_codec = &fields[0].data_type().codec;
+            assert!(matches!(first_field_codec, Codec::Utf8View));
+        } else {
+            panic!("Expected Struct codec");
+        }
+    }
+}
diff --git a/arrow-avro/src/lib.rs b/arrow-avro/src/lib.rs
index 7c594cb0a854..e413e0aa9173 100644
--- a/arrow-avro/src/lib.rs
+++ b/arrow-avro/src/lib.rs
@@ -33,22 +33,40 @@
 /// Implements the primary reader interface and record decoding logic.
 pub mod reader;
 
-/// Avro schema parsing and representation
-///
-/// Provides types for parsing and representing Avro schema definitions.
+// Avro schema parsing and representation
+//
+// Provides types for parsing and representing Avro schema definitions.
 mod schema;
 
 /// Compression codec implementations for Avro
 ///
 /// Provides support for various compression algorithms used in Avro files,
 /// including Deflate, Snappy, and ZStandard.
-mod compression;
+pub mod compression;
 
 /// Data type conversions between Avro and Arrow types
 ///
 /// This module contains the necessary types and functions to convert between
 /// Avro data types and Arrow data types.
-mod codec;
+pub mod codec;
+
+pub use reader::ReadOptions;
+
+/// Extension trait for AvroField to add Utf8View support
+///
+/// This trait adds methods for working with Utf8View support to the AvroField struct.
+pub trait AvroFieldExt {
+    /// Returns a new field with Utf8View support enabled for string data
+    ///
+    /// This will convert any string data to use StringViewArray instead of StringArray.
+    fn with_utf8view(&self) -> Self;
+}
+
+impl AvroFieldExt for codec::AvroField {
+    fn with_utf8view(&self) -> Self {
+        codec::AvroField::with_utf8view(self)
+    }
+}
 
 #[cfg(test)]
 mod test_util {
diff --git a/arrow-avro/src/reader/mod.rs b/arrow-avro/src/reader/mod.rs
index 12fa67d9c8e3..61e3e8511caa 100644
--- a/arrow-avro/src/reader/mod.rs
+++ b/arrow-avro/src/reader/mod.rs
@@ -22,14 +22,55 @@ use crate::reader::header::{Header, HeaderDecoder};
 use arrow_schema::ArrowError;
 use std::io::BufRead;
 
-mod header;
-
 mod block;
-
 mod cursor;
+mod header;
 mod record;
 mod vlq;
 
+/// Configuration options for reading Avro data into Arrow arrays
+///
+/// This struct contains configuration options that control how Avro data is
+/// converted into Arrow arrays. It allows customizing various aspects of the
+/// data conversion process.
+///
+/// # Examples
+///
+/// ```
+/// # use arrow_avro::reader::ReadOptions;
+/// // Use default options (regular StringArray for strings)
+/// let default_options = ReadOptions::default();
+///
+/// // Enable Utf8View support for better string performance
+/// let options = ReadOptions::default()
+///     .with_utf8view(true);
+/// ```
+#[derive(Default, Debug, Clone)]
+pub struct ReadOptions {
+    use_utf8view: bool,
+}
+
+impl ReadOptions {
+    /// Create a new `ReadOptions` with default values
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Set whether to use StringViewArray for string data
+    ///
+    /// When enabled, string data from Avro files will be loaded into
+    /// Arrow's StringViewArray instead of the standard StringArray.
+    pub fn with_utf8view(mut self, use_utf8view: bool) -> Self {
+        self.use_utf8view = use_utf8view;
+        self
+    }
+
+    /// Get whether StringViewArray is enabled for string data
+    pub fn use_utf8view(&self) -> bool {
+        self.use_utf8view
+    }
+}
+
 /// Read a [`Header`] from the provided [`BufRead`]
 fn read_header<R: BufRead>(mut reader: R) -> Result<Header, ArrowError> {
     let mut decoder = HeaderDecoder::default();
@@ -75,24 +116,36 @@ fn read_blocks<R: BufRead>(mut reader: R) -> impl Iterator<Item = Result<Block,
 
 #[cfg(test)]
 mod test {
-    use crate::codec::AvroField;
+    use crate::codec::{AvroDataType, AvroField, Codec};
     use crate::compression::CompressionCodec;
     use crate::reader::record::RecordDecoder;
     use crate::reader::{read_blocks, read_header};
     use crate::test_util::arrow_test_data;
     use arrow_array::*;
+    use arrow_schema::{DataType, Field};
+    use std::collections::HashMap;
     use std::fs::File;
     use std::io::BufReader;
     use std::sync::Arc;
 
     fn read_file(file: &str, batch_size: usize) -> RecordBatch {
+        read_file_with_options(file, batch_size, &crate::ReadOptions::default())
+    }
+
+    fn read_file_with_options(
+        file: &str,
+        batch_size: usize,
+        options: &crate::ReadOptions,
+    ) -> RecordBatch {
         let file = File::open(file).unwrap();
         let mut reader = BufReader::new(file);
         let header = read_header(&mut reader).unwrap();
         let compression = header.compression().unwrap();
         let schema = header.schema().unwrap().unwrap();
         let root = AvroField::try_from(&schema).unwrap();
-        let mut decoder = RecordDecoder::try_new(root.data_type()).unwrap();
+
+        let mut decoder =
+            RecordDecoder::try_new_with_options(root.data_type(), options.clone()).unwrap();
 
         for result in read_blocks(reader) {
             let block = result.unwrap();
@@ -116,6 +169,46 @@ mod test {
         decoder.flush().unwrap()
     }
 
+    #[test]
+    fn test_utf8view_support() {
+        let schema_json = r#"{
+            "type": "record",
+            "name": "test",
+            "fields": [{
+                "name": "str_field",
+                "type": "string"
+            }]
+        }"#;
+
+        let schema: crate::schema::Schema = serde_json::from_str(schema_json).unwrap();
+        let avro_field = AvroField::try_from(&schema).unwrap();
+
+        let data_type = avro_field.data_type();
+
+        struct TestHelper;
+        impl TestHelper {
+            fn with_utf8view(field: &Field) -> Field {
+                match field.data_type() {
+                    DataType::Utf8 => {
+                        Field::new(field.name(), DataType::Utf8View, field.is_nullable())
+                            .with_metadata(field.metadata().clone())
+                    }
+                    _ => field.clone(),
+                }
+            }
+        }
+
+        let field = TestHelper::with_utf8view(&Field::new("str_field", DataType::Utf8, false));
+
+        assert_eq!(field.data_type(), &DataType::Utf8View);
+
+        let array = StringViewArray::from(vec!["test1", "test2"]);
+        let batch =
+            RecordBatch::try_from_iter(vec![("str_field", Arc::new(array) as ArrayRef)]).unwrap();
+
+        assert!(batch.column(0).as_any().is::<StringViewArray>());
+    }
+
     #[test]
     fn test_alltypes() {
         let files = [
diff --git a/arrow-avro/src/reader/record.rs b/arrow-avro/src/reader/record.rs
index debe252b2b81..d5d454c8eeb9 100644
--- a/arrow-avro/src/reader/record.rs
+++ b/arrow-avro/src/reader/record.rs
@@ -19,6 +19,7 @@ use crate::codec::{AvroDataType, Codec, Nullability};
 use crate::reader::block::{Block, BlockDecoder};
 use crate::reader::cursor::AvroCursor;
 use crate::reader::header::Header;
+use crate::reader::ReadOptions;
 use crate::schema::*;
 use arrow_array::types::*;
 use arrow_array::*;
@@ -34,15 +35,31 @@ use std::sync::Arc;
 pub struct RecordDecoder {
     schema: SchemaRef,
     fields: Vec<Decoder>,
+    use_utf8view: bool,
 }
 
 impl RecordDecoder {
-    /// Create a new [`RecordDecoder`] from the provided [`AvroDataType`]
+    /// Create a new [`RecordDecoder`] from the provided [`AvroDataType`] with default options
     pub fn try_new(data_type: &AvroDataType) -> Result<Self, ArrowError> {
+        Self::try_new_with_options(data_type, ReadOptions::default())
+    }
+
+    /// Create a new [`RecordDecoder`] from the provided [`AvroDataType`] with additional options
+    ///
+    /// This method allows you to customize how the Avro data is decoded into Arrow arrays.
+    ///
+    /// # Parameters
+    /// * `data_type` - The Avro data type to decode
+    /// * `options` - Configuration options for decoding
+    pub fn try_new_with_options(
+        data_type: &AvroDataType,
+        options: ReadOptions,
+    ) -> Result<Self, ArrowError> {
         match Decoder::try_new(data_type)? {
             Decoder::Record(fields, encodings) => Ok(Self {
                 schema: Arc::new(ArrowSchema::new(fields)),
                 fields: encodings,
+                use_utf8view: options.use_utf8view(),
             }),
             encoding => Err(ArrowError::ParseError(format!(
                 "Expected record got {encoding:?}"
@@ -91,7 +108,10 @@ enum Decoder {
     TimestampMillis(bool, Vec<i64>),
     TimestampMicros(bool, Vec<i64>),
     Binary(OffsetBufferBuilder<i32>, Vec<u8>),
+    /// String data encoded as UTF-8 bytes, mapped to Arrow's StringArray
     String(OffsetBufferBuilder<i32>, Vec<u8>),
+    /// String data encoded as UTF-8 bytes, but mapped to Arrow's StringViewArray
+    StringView(OffsetBufferBuilder<i32>, Vec<u8>),
     List(FieldRef, OffsetBufferBuilder<i32>, Box<Decoder>),
     Record(Fields, Vec<Decoder>),
     Nullable(Nullability, NullBufferBuilder, Box<Decoder>),
@@ -116,6 +136,10 @@ impl Decoder {
                 OffsetBufferBuilder::new(DEFAULT_CAPACITY),
                 Vec::with_capacity(DEFAULT_CAPACITY),
             ),
+            Codec::Utf8View => Self::StringView(
+                OffsetBufferBuilder::new(DEFAULT_CAPACITY),
+                Vec::with_capacity(DEFAULT_CAPACITY),
+            ),
             Codec::Date32 => Self::Date32(Vec::with_capacity(DEFAULT_CAPACITY)),
             Codec::TimeMillis => Self::TimeMillis(Vec::with_capacity(DEFAULT_CAPACITY)),
             Codec::TimeMicros => Self::TimeMicros(Vec::with_capacity(DEFAULT_CAPACITY)),
@@ -169,7 +193,9 @@ impl Decoder {
             | Self::TimestampMicros(_, v) => v.push(0),
             Self::Float32(v) => v.push(0.),
             Self::Float64(v) => v.push(0.),
-            Self::Binary(offsets, _) | Self::String(offsets, _) => offsets.push_length(0),
+            Self::Binary(offsets, _) | Self::String(offsets, _) | Self::StringView(offsets, _) => {
+                offsets.push_length(0);
+            }
             Self::List(_, offsets, e) => {
                 offsets.push_length(0);
                 e.append_null();
@@ -193,7 +219,9 @@ impl Decoder {
             | Self::TimestampMicros(_, values) => values.push(buf.get_long()?),
             Self::Float32(values) => values.push(buf.get_float()?),
             Self::Float64(values) => values.push(buf.get_double()?),
-            Self::Binary(offsets, values) | Self::String(offsets, values) => {
+            Self::Binary(offsets, values)
+            | Self::String(offsets, values)
+            | Self::StringView(offsets, values) => {
                 let data = buf.get_bytes()?;
                 offsets.push_length(data.len());
                 values.extend_from_slice(data);
@@ -256,6 +284,23 @@ impl Decoder {
                 let values = flush_values(values).into();
                 Arc::new(StringArray::new(offsets, values, nulls))
             }
+            Self::StringView(offsets, values) => {
+                let offsets = flush_offsets(offsets);
+                let values = flush_values(values);
+                let array = StringArray::new(offsets, values.into(), nulls.clone());
+
+                let values: Vec<&str> = (0..array.len())
+                    .map(|i| {
+                        if array.is_valid(i) {
+                            array.value(i)
+                        } else {
+                            ""
+                        }
+                    })
+                    .collect();
+
+                Arc::new(StringViewArray::from(values))
+            }
             Self::List(field, offsets, values) => {
                 let values = values.flush(None)?;
                 let offsets = flush_offsets(offsets);


Reviews:
user: login: alamb
body: Thank you @kumarlokesh  -- this looks really nice

To make reviewing this PR easier,  is there any way you can break it into multiple smaller PRs? Specifically, 
1. Improve the comments
2. Support for Map
3. Support for Utf8View?

Also it is not clear to me how much of a conflict this PR would have with:
- https://github.com/apache/arrow-rs/pull/6965
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T17:55:55Z
user: login: alamb
body: Thank you @kumarlokesh  -- I think this looks really nicely documented and tested. 

I had a few API questions / suggestions but I don't think they are necessary.

It seems to me that this PR does not change any APIs (only adds new ones) so I think we could merge it before the next major release. What do you think?

Thanks again for pushing this along and breaking it up into smaller chunks
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T20:19:46Z
user: login: alamb
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T11:13:26Z
user: login: alamb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T11:14:51Z

Review comments:
diff_hunk: @@ -51,10 +51,21 @@ impl AvroDataType {
         Field::new(name, d, self.nullability.is_some()).with_metadata(self.metadata.clone())
     }
 
+    /// Returns a reference to the codec used by this data type
+    ///
+    /// The codec determines how Avro data is encoded and mapped to Arrow data types.
+    /// This is useful when you need to inspect or use the specific encoding of a field.
     pub fn codec(&self) -> &Codec {
         &self.codec
     }
 
+    /// Returns the nullability status of this data type
path: arrow-avro/src/codec.rs
user: login: alamb
body: This is really nice -- thank you
created_at: 2025-04-28T17:52:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -284,13 +391,55 @@ fn make_data_type<'a>(
             ComplexType::Enum(e) => Err(ArrowError::NotYetImplemented(format!(
                 "Enum of {e:?} not currently supported"
             ))),
-            ComplexType::Map(m) => Err(ArrowError::NotYetImplemented(format!(
-                "Map of {m:?} not currently supported"
-            ))),
+            ComplexType::Map(m) => {
path: arrow-avro/src/codec.rs
user: login: alamb
body: this seems to be a new feature (not just string view, but also map support)

Have you had a chance to review  this PR?
- https://github.com/apache/arrow-rs/pull/6965 ?


created_at: 2025-04-28T17:53:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,9 +23,16 @@ use std::io::Read;
 pub const CODEC_METADATA_KEY: &str = "avro.codec";
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
+/// Supported compression codecs for Avro data
path: arrow-avro/src/compression.rs
user: login: alamb
body: Thank you for these comments
created_at: 2025-04-28T17:55:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -364,3 +437,213 @@ fn make_data_type<'a>(
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::schema::{
+        Attributes, ComplexType, Fixed, PrimitiveType, Record, Schema, Type, TypeName,
+    };
+    use serde_json;
+    use std::collections::HashMap;
+
+    fn create_schema_with_logical_type(
+        primitive_type: PrimitiveType,
+        logical_type: &'static str,
+    ) -> Schema<'static> {
+        let attributes = Attributes {
+            logical_type: Some(logical_type),
+            additional: Default::default(),
+        };
+
+        Schema::Type(Type {
+            r#type: TypeName::Primitive(primitive_type),
+            attributes,
+        })
+    }
+
+    fn create_fixed_schema(size: usize, logical_type: &'static str) -> Schema<'static> {
+        let attributes = Attributes {
+            logical_type: Some(logical_type),
+            additional: Default::default(),
+        };
+
+        Schema::Complex(ComplexType::Fixed(Fixed {
+            name: "fixed_type",
+            namespace: None,
+            aliases: Vec::new(),
+            size,
+            attributes,
+        }))
+    }
+
+    #[test]
+    fn test_date_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Int, "date");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::Date32));
+    }
+
+    #[test]
+    fn test_time_millis_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Int, "time-millis");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimeMillis));
+    }
+
+    #[test]
+    fn test_time_micros_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Long, "time-micros");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimeMicros));
+    }
+
+    #[test]
+    fn test_timestamp_millis_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Long, "timestamp-millis");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimestampMillis(true)));
+    }
+
+    #[test]
+    fn test_timestamp_micros_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Long, "timestamp-micros");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimestampMicros(true)));
+    }
+
+    #[test]
+    fn test_local_timestamp_millis_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Long, "local-timestamp-millis");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimestampMillis(false)));
+    }
+
+    #[test]
+    fn test_local_timestamp_micros_logical_type() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Long, "local-timestamp-micros");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert!(matches!(result.codec, Codec::TimestampMicros(false)));
+    }
+
+    #[test]
+    fn test_duration_logical_type() {
+        let mut codec = Codec::Fixed(12);
+
+        if let c @ Codec::Fixed(12) = &mut codec {
+            *c = Codec::Interval;
+        }
+
+        assert!(matches!(codec, Codec::Interval));
+    }
+
+    #[test]
+    fn test_decimal_logical_type_not_implemented() {
+        let mut codec = Codec::Fixed(16);
+
+        let process_decimal = || -> Result<(), ArrowError> {
+            if let Codec::Fixed(_) = codec {
+                return Err(ArrowError::NotYetImplemented(
+                    "Decimals are not currently supported".to_string(),
+                ));
+            }
+            Ok(())
+        };
+
+        let result = process_decimal();
+
+        assert!(result.is_err());
+        if let Err(ArrowError::NotYetImplemented(msg)) = result {
+            assert!(msg.contains("Decimals are not currently supported"));
+        } else {
+            panic!("Expected NotYetImplemented error");
+        }
+    }
+
+    #[test]
+    fn test_unknown_logical_type_added_to_metadata() {
+        let schema = create_schema_with_logical_type(PrimitiveType::Int, "custom-type");
+
+        let mut resolver = Resolver::default();
+        let result = make_data_type(&schema, None, &mut resolver, false).unwrap();
+
+        assert_eq!(
+            result.metadata.get("logicalType"),
+            Some(&"custom-type".to_string())
+        );
+    }
+
+    #[test]
+    fn test_string_with_utf8view_enabled() {
path: arrow-avro/src/codec.rs
user: login: alamb
body: 👍 
created_at: 2025-05-19T20:17:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,19 +36,37 @@ pub mod reader;
 /// Avro schema parsing and representation
 ///
 /// Provides types for parsing and representing Avro schema definitions.
-mod schema;
+pub mod schema;
path: arrow-avro/src/lib.rs
user: login: alamb
body: is it needed to make these module public?
created_at: 2025-05-19T20:17:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,15 +34,32 @@ use std::sync::Arc;
 pub struct RecordDecoder {
     schema: SchemaRef,
     fields: Vec<Decoder>,
+    use_utf8view: bool,
 }
 
 impl RecordDecoder {
     /// Create a new [`RecordDecoder`] from the provided [`AvroDataType`]
     pub fn try_new(data_type: &AvroDataType) -> Result<Self, ArrowError> {
+        Self::try_new_with_options(data_type, false)
+    }
+
+    /// Create a new [`RecordDecoder`] from the provided [`AvroDataType`] with additional options
+    ///
+    /// This method allows you to customize how the Avro data is decoded into Arrow arrays.
+    /// In particular, it allows enabling Utf8View support for better string performance.
+    ///
+    /// # Parameters
+    /// * `data_type` - The Avro data type to decode
+    /// * `use_utf8view` - If true, use StringViewArray instead of StringArray for string data
+    pub fn try_new_with_options(
+        data_type: &AvroDataType,
+        use_utf8view: bool,
path: arrow-avro/src/reader/record.rs
user: login: alamb
body: Shall we use `ReadOptions` here rather than the single field? That would make updating these easier in the future
created_at: 2025-05-19T20:18:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,14 +22,42 @@ use crate::reader::header::{Header, HeaderDecoder};
 use arrow_schema::ArrowError;
 use std::io::BufRead;
 
-mod header;
-
 mod block;
-
 mod cursor;
+mod header;
 mod record;
 mod vlq;
 
+/// Configuration options for reading Avro data into Arrow arrays
+///
+/// This struct contains configuration options that control how Avro data is
+/// converted into Arrow arrays. It allows customizing various aspects of the
+/// data conversion process.
+///
+/// # Examples
+///
+/// ```
+/// # use arrow_avro::reader::ReadOptions;
+/// // Use default options (regular StringArray for strings)
+/// let default_options = ReadOptions::default();
+///
+/// // Enable Utf8View support for better string performance
+/// let options = ReadOptions {
+///     use_utf8view: true,
+///     ..ReadOptions::default()
+/// };
+/// ```
+#[derive(Default)]
+pub struct ReadOptions {
+    /// If true, use StringViewArray instead of StringArray for string data
+    ///
+    /// When this option is enabled, string data from Avro files will be loaded
+    /// into Arrow's StringViewArray instead of the standard StringArray.
+    ///
+    /// Default: false
+    pub use_utf8view: bool,
path: arrow-avro/src/reader/mod.rs
user: login: alamb
body: I think it would be nice if this field was not pub -- that way we could add new options to `ReadOptions` in the future without it being a breaking API change
created_at: 2025-05-19T20:18:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,271 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Comprehensive benchmarks comparing StringArray vs StringViewArray performance
+//!
+//! This benchmark suite compares the performance characteristics of StringArray vs
+//! StringViewArray across three key dimensions:
+//! 1. Array creation performance
+//! 2. String value access operations  
+//! 3. Avro file reading with each array type
+
+use std::fs::File;
+use std::io::{BufReader, Read, Write};
+use std::sync::Arc;
+use std::time::Duration;
+
+use arrow::array::RecordBatch;
+use arrow::datatypes::{DataType, Field, Schema};
+use arrow_array::{ArrayRef, Int32Array, StringArray, StringViewArray};
+use arrow_avro::ReadOptions;
+use arrow_schema::ArrowError;
+use criterion::*;
+use tempfile::NamedTempFile;
+
+fn create_test_data(count: usize, str_length: usize) -> Vec<String> {
+    (0..count)
+        .map(|i| format!("str_{}", i) + &"a".repeat(str_length))
+        .collect()
+}
+
+fn create_avro_test_file(row_count: usize, str_length: usize) -> Result<NamedTempFile, ArrowError> {
+    let schema = Arc::new(Schema::new(vec![
+        Field::new("string_field", DataType::Utf8, false),
+        Field::new("int_field", DataType::Int32, false),
+    ]));
+
+    let strings = create_test_data(row_count, str_length);
+    let string_array = StringArray::from_iter(strings.iter().map(|s| Some(s.as_str())));
+    let int_array = Int32Array::from_iter_values(0..row_count as i32);
+    let _batch = RecordBatch::try_new(
+        schema.clone(),
+        vec![
+            Arc::new(string_array) as ArrayRef,
+            Arc::new(int_array) as ArrayRef,
+        ],
+    )?;
+
+    let temp_file = NamedTempFile::new()?;
+
+    let mut file = temp_file.reopen()?;
+
+    file.write_all(b"AVRO")?;
path: arrow-avro/benches/avro_reader.rs
user: login: alamb
body: I wondered why this benchmark isn't using a avro writer and then I realized it is because there is no writer in avro-arrow 👍 
created_at: 2025-05-21T11:13:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -139,6 +155,11 @@ pub enum Codec {
     Binary,
     /// String data represented as UTF-8 encoded bytes, corresponding to Arrow's StringArray
     Utf8,
+    /// String data represented as UTF-8 encoded bytes with an optimized view representation,
path: arrow-avro/src/codec.rs
user: login: alamb
body: This is not pub it seems: https://docs.rs/arrow-avro/latest/arrow_avro/?search=Codec

Though now I look it seems there is nothing that is pub: https://docs.rs/arrow-avro/latest/arrow_avro/all.html 🤔 
created_at: 2025-05-21T11:14:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: kumarlokesh
created_at: 2025-04-28T20:22:50Z
body: @alamb moved improvements to comments to another PR https://github.com/apache/arrow-rs/pull/7449
author_association: CONTRIBUTOR
user: login: kumarlokesh
created_at: 2025-05-18T19:22:50Z
body: @alamb updated this PR to remove `"2. Support for Map"`. The sole focus of this PR is `"3. Support for Utf8View"`.

In terms of scope of work between between #7434 and #6965, don't see a major overlap between what's intended. Therefore, I think the code conflict between the two should be minimal, and fairly trivial to resolve.

> Thank you @kumarlokesh -- this looks really nice
> 
> To make reviewing this PR easier, is there any way you can break it into multiple smaller PRs? Specifically,
> 
> 1. Improve the comments
> 2. Support for Map
> 3. Support for Utf8View?
> 
> Also it is not clear to me how much of a conflict this PR would have with:
> 
> * [Avro codec enhancements #6965](https://github.com/apache/arrow-rs/pull/6965)


author_association: CONTRIBUTOR
user: login: alamb
created_at: 2025-05-19T20:34:18Z
body: Marking as next-major-release as a precaution to avoid merging until we convince ourselves this PR has no breaking changes
author_association: CONTRIBUTOR
user: login: kumarlokesh
created_at: 2025-05-20T05:44:50Z
body: > Thank you @kumarlokesh -- I think this looks really nicely documented and tested.
> 
> I had a few API questions / suggestions but I don't think they are necessary.
> 
> It seems to me that this PR does not change any APIs (only adds new ones) so I think we could merge it before the next major release. What do you think?
> 
> Thanks again for pushing this along and breaking it up into smaller chunks

@alamb thanks for the review! Yes, I think this PR could be merged before the next major release.

Addressed review comments here https://github.com/apache/arrow-rs/pull/7434/commits/cf50b5b851ce2e1cb557e5ab3cdb16f6806663e3
author_association: CONTRIBUTOR
user: login: alamb
created_at: 2025-05-21T11:16:08Z
body: Let's keep thing moving and merge this PR. 

@tustvold do you remember what the plan / status of the `arrow-avro` crate is? Did we mark it as experimental or something so we could change the APIs between major releases?
author_association: CONTRIBUTOR
user: login: alamb
created_at: 2025-05-21T11:16:22Z
body: Thanks again @kumarlokesh 
author_association: CONTRIBUTOR