Skip to main content

arrow_avro/
lib.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Convert data to / from the [Apache Arrow] memory format and [Apache Avro].
19//!
20//! This crate provides:
21//! - a [`reader`] that decodes Avro (Object Container Files, unframed binary datums,
22//!   Avro Single‑Object encoding, and Confluent Schema Registry wire format) into Arrow
23//!   `RecordBatch`es,
24//! - and a [`writer`] that encodes Arrow `RecordBatch`es into Avro (OCF or SOE).
25//!
26//! If you’re new to Arrow or Avro, see:
27//! - Arrow project site: <https://arrow.apache.org/>
28//! - Avro 1.11.1 specification: <https://avro.apache.org/docs/1.11.1/specification/>
29//!
30//! ## Example: OCF (Object Container File) round‑trip *(runnable)*
31//!
32//! The example below creates an Arrow table, writes an **Avro OCF** fully in memory,
33//! and then reads it back. OCF is a self‑describing file format that embeds the Avro
34//! schema in a header with optional compression and block sync markers.
35//! Spec: <https://avro.apache.org/docs/1.11.1/specification/#object-container-files>
36//!
37//! ```
38//! use std::io::Cursor;
39//! use std::sync::Arc;
40//! use arrow_array::{ArrayRef, Int32Array, RecordBatch};
41//! use arrow_schema::{DataType, Field, Schema};
42//! use arrow_avro::writer::AvroWriter;
43//! use arrow_avro::reader::ReaderBuilder;
44//!
45//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
46//! // Build a tiny Arrow batch
47//! let schema = Schema::new(vec![Field::new("id", DataType::Int32, false)]);
48//! let batch = RecordBatch::try_new(
49//!     Arc::new(schema.clone()),
50//!     vec![Arc::new(Int32Array::from(vec![1, 2, 3])) as ArrayRef],
51//! )?;
52//!
53//! // Write an Avro **Object Container File** (OCF) to a Vec<u8>
54//! let sink: Vec<u8> = Vec::new();
55//! let mut w = AvroWriter::new(sink, schema.clone())?;
56//! w.write(&batch)?;
57//! w.finish()?;
58//! let bytes = w.into_inner();
59//! assert!(!bytes.is_empty());
60//!
61//! // Read it back
62//! let mut r = ReaderBuilder::new().build(Cursor::new(bytes))?;
63//! let out = r.next().unwrap()?;
64//! assert_eq!(out.num_rows(), 3);
65//! # Ok(()) }
66//! ```
67//!
68//! ## Quickstart: unframed Avro datums *(runnable)*
69//!
70//! Kafka messages and other transports can contain bare Avro records without an OCF header,
71//! single-object prefix, or schema-registry framing. When the writer schema is already known,
72//! register it, select its fingerprint and [`reader::DecoderMode::UnframedDatum`], and call
73//! [`reader::Decoder::decode`] once per record. The returned byte count also supports
74//! consecutive datums in one buffer.
75//!
76//! ```
77//! use arrow_array::{Array, Int64Array};
78//! use arrow_avro::reader::{DecoderMode, ReaderBuilder};
79//! use arrow_avro::schema::{AvroSchema, SchemaStore};
80//!
81//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
82//! let schema = AvroSchema::new(
83//!     r#"{"type":"record","name":"Event","fields":[{"name":"id","type":"long"}]}"#
84//!         .to_string(),
85//! );
86//! let mut store = SchemaStore::new();
87//! let fingerprint = store.register(schema)?;
88//! let mut decoder = ReaderBuilder::new()
89//!     .with_writer_schema_store(store)
90//!     .with_active_fingerprint(fingerprint)
91//!     .with_decoder_mode(DecoderMode::UnframedDatum)
92//!     .build_decoder()?;
93//!
94//! // Two consecutive records, {id: 7} and {id: 42}, in Avro zigzag encoding.
95//! let mut remaining: &[u8] = &[0x0e, 0x54];
96//! while !remaining.is_empty() {
97//!     let consumed = decoder.decode(remaining)?;
98//!     remaining = &remaining[consumed..];
99//! }
100//!
101//! let batch = decoder.flush()?.expect("decoded records");
102//! let ids = batch.column(0).as_any().downcast_ref::<Int64Array>().unwrap();
103//! assert_eq!(ids.values(), &[7, 42]);
104//! # Ok(()) }
105//! ```
106//!
107//! ## Quickstart: SOE (Single‑Object Encoding) round‑trip *(runnable)*
108//!
109//! Avro **Single‑Object Encoding (SOE)** wraps an Avro body with a 2‑byte marker
110//! `0xC3 0x01` and an **8‑byte little‑endian CRC‑64‑AVRO Rabin fingerprint** of the
111//! writer schema, then the Avro body. Spec:
112//! <https://avro.apache.org/docs/1.11.1/specification/#single-object-encoding>
113//!
114//! This example registers the writer schema (computing a Rabin fingerprint), writes a
115//! single‑row Avro body (using `AvroStreamWriter`), constructs the SOE frame, and decodes it back to Arrow.
116//!
117//! ```
118//! use std::collections::HashMap;
119//! use std::sync::Arc;
120//! use arrow_array::{ArrayRef, Int64Array, RecordBatch};
121//! use arrow_schema::{DataType, Field, Schema};
122//! use arrow_avro::writer::{AvroStreamWriter, WriterBuilder};
123//! use arrow_avro::reader::ReaderBuilder;
124//! use arrow_avro::schema::{AvroSchema, SchemaStore, FingerprintStrategy, SCHEMA_METADATA_KEY};
125//!
126//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
127//! // Writer schema: { "type":"record","name":"User","fields":[{"name":"x","type":"long"}] }
128//! let writer_json = r#"{"type":"record","name":"User","fields":[{"name":"x","type":"long"}]}"#;
129//! let mut store = SchemaStore::new(); // Rabin CRC‑64‑AVRO by default
130//! let _fp = store.register(AvroSchema::new(writer_json.to_string()))?;
131//!
132//! // Build an Arrow schema that references the same Avro JSON
133//! let mut md = HashMap::new();
134//! md.insert(SCHEMA_METADATA_KEY.to_string(), writer_json.to_string());
135//! let schema = Schema::new_with_metadata(
136//!     vec![Field::new("x", DataType::Int64, false)],
137//!     md,
138//! );
139//!
140//! // One‑row batch: { x: 7 }
141//! let batch = RecordBatch::try_new(
142//!     Arc::new(schema.clone()),
143//!     vec![Arc::new(Int64Array::from(vec![7])) as ArrayRef],
144//! )?;
145//!
146//! // Stream‑write a single record; the writer adds **SOE** (C3 01 + Rabin) automatically.
147//! let sink: Vec<u8> = Vec::new();
148//! let mut w: AvroStreamWriter<Vec<u8>> = WriterBuilder::new(schema.clone())
149//!     .with_fingerprint_strategy(FingerprintStrategy::Rabin)
150//!     .build(sink)?;
151//! w.write(&batch)?;
152//! w.finish()?;
153//! let frame = w.into_inner(); // already: C3 01 + 8B LE Rabin + Avro body
154//! assert!(frame.len() > 10);
155//!
156//! // Decode
157//! let mut dec = ReaderBuilder::new()
158//!   .with_writer_schema_store(store)
159//!   .build_decoder()?;
160//! dec.decode(&frame)?;
161//! let out = dec.flush()?.expect("one row");
162//! assert_eq!(out.num_rows(), 1);
163//! # Ok(()) }
164//! ```
165//!
166//! ## `async` Reading (`async` feature)
167//!
168//! The [`reader`] module provides async APIs for reading Avro files when the `async`
169//! feature is enabled.
170//!
171//! [`AsyncAvroFileReader`] implements `Stream<Item = Result<RecordBatch, ArrowError>>`,
172//! allowing efficient async streaming of record batches. Any [`AsyncFileReader`]
173//! can be used as the source; there is a built-in implementation for types
174//! implementing [`AsyncRead`] + [`AsyncSeek`] (such as [`tokio::fs::File`]), and object
175//! storage services such as S3 can be integrated by implementing
176//! [`AsyncFileReader`] on top of a client such as the [object_store] crate
177//! (see the example on the trait documentation).
178//!
179//! [`AsyncRead`]: tokio::io::AsyncRead
180//! [`AsyncSeek`]: tokio::io::AsyncSeek
181//! [`tokio::fs::File`]: https://docs.rs/tokio/latest/tokio/fs/struct.File.html
182//!
183//! ```ignore
184//! use arrow_avro::reader::AsyncAvroFileReader;
185//! use futures::TryStreamExt;
186//!
187//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
188//! let file = tokio::fs::File::open("data/example.avro").await?;
189//! let file_size = file.metadata().await?.len();
190//!
191//! let stream = AsyncAvroFileReader::builder(file, file_size, 1024)
192//!     .try_build()
193//!     .await?;
194//!
195//! let batches: Vec<_> = stream.try_collect().await?;
196//! # Ok(())
197//! # }
198//! ```
199//!
200//! [object_store]: https://docs.rs/object_store/latest/object_store/
201//!
202//! ---
203//!
204//! ### Modules
205//!
206//! - [`reader`]: read Avro (OCF, unframed datums, SOE, Confluent) into Arrow `RecordBatch`es.
207//!   - With the `async` feature: [`AsyncAvroFileReader`] for async streaming reads,
208//!     from any [`AsyncFileReader`] source including cloud object storage.
209//! - [`writer`]: write Arrow `RecordBatch`es as Avro (OCF, SOE, Confluent, Apicurio).
210//! - [`schema`]: Avro schema parsing / fingerprints / registries.
211//! - [`compression`]: codecs used for **OCF block compression** (i.e., Deflate, Snappy, Zstandard, BZip2, and XZ).
212//! - [`codec`]: internal Avro-Arrow type conversion and row decode/encode plans.
213//!
214//! [`AsyncAvroFileReader`]: reader::AsyncAvroFileReader
215//! [`AsyncFileReader`]: reader::AsyncFileReader
216//!
217//! ### Features
218//!
219//! **OCF compression (enabled by default)**
220//! - `deflate` — enable DEFLATE block compression (via `flate2`).
221//! - `snappy` — enable Snappy block compression with 4‑byte BE CRC32 (per Avro).
222//! - `zstd` — enable Zstandard block compression.
223//! - `bzip2` — enable BZip2 block compression.
224//! - `xz` — enable XZ/LZMA block compression.
225//!
226//! **Async (opt‑in)**
227//! - `async` — enable async APIs for reading Avro (`AsyncAvroFileReader`, `AsyncFileReader` trait).
228//!   Cloud storage (S3, GCS, Azure Blob, etc.) can be integrated by implementing
229//!   `AsyncFileReader` on top of a client such as the [`object_store`] crate.
230//! - `object_store` (**deprecated**): enables the deprecated `AvroObjectReader`.
231//!   Implement `AsyncFileReader` directly instead (see above). Implies `async`.
232//!   This feature will be removed in a future release.
233//!
234//! **Schema fingerprints & helpers (opt‑in)**
235//! - `md5` — enable MD5 writer‑schema fingerprints.
236//! - `sha256` — enable SHA‑256 writer‑schema fingerprints.
237//! - `small_decimals` — support for compact Arrow representations of small Avro decimals (`Decimal32` and `Decimal64`).
238//! - `avro_custom_types` — interpret Avro fields annotated with Arrow‑specific logical
239//!   types such as `arrow.duration-nanos`, `arrow.duration-micros`,
240//!   `arrow.duration-millis`, or `arrow.duration-seconds` as Arrow `Duration(TimeUnit)`.
241//! - `canonical_extension_types` — enable support for Arrow [canonical extension types]
242//!   from `arrow-schema` so `arrow-avro` can respect them during Avro↔Arrow mapping.
243//!
244//! **Notes**
245//! - OCF compression codecs apply only to **Object Container Files**; they do not affect Avro
246//!   single object encodings.
247//!
248//! [`object_store`]: https://docs.rs/object_store/latest/object_store/
249//!
250//! [canonical extension types]: https://arrow.apache.org/docs/format/CanonicalExtensions.html
251//!
252//! [Apache Arrow]: https://arrow.apache.org/
253//! [Apache Avro]: https://avro.apache.org/
254
255#![doc(
256    html_logo_url = "https://arrow.apache.org/img/arrow-logo_chevrons_black-txt_white-bg.svg",
257    html_favicon_url = "https://arrow.apache.org/img/arrow-logo_chevrons_black-txt_transparent-bg.svg"
258)]
259#![cfg_attr(docsrs, feature(doc_cfg))]
260#![warn(missing_docs)]
261
262/// Core functionality for reading Avro data into Arrow arrays
263///
264/// Implements the primary reader interface and record decoding logic.
265pub mod reader;
266
267/// Core functionality for writing Arrow arrays as Avro data
268///
269/// Implements the primary writer interface and record encoding logic.
270pub mod writer;
271
272/// Avro schema parsing and representation
273///
274/// Provides types for parsing and representing Avro schema definitions.
275pub mod schema;
276
277/// Compression codec implementations for Avro
278///
279/// Provides support for various compression algorithms used in Avro files,
280/// including Deflate, Snappy, and ZStandard.
281pub mod compression;
282
283/// Data type conversions between Avro and Arrow types
284///
285/// This module contains the necessary types and functions to convert between
286/// Avro data types and Arrow data types.
287pub mod codec;
288
289/// AvroError variants
290pub mod errors;
291
292/// Extension trait for AvroField to add Utf8View support
293///
294/// This trait adds methods for working with Utf8View support to the AvroField struct.
295pub trait AvroFieldExt {
296    /// Returns a new field with Utf8View support enabled for string data
297    ///
298    /// This will convert any string data to use StringViewArray instead of StringArray.
299    fn with_utf8view(&self) -> Self;
300}
301
302impl AvroFieldExt for codec::AvroField {
303    fn with_utf8view(&self) -> Self {
304        codec::AvroField::with_utf8view(self)
305    }
306}
307
308#[cfg(test)]
309mod test_util {
310    pub fn arrow_test_data(path: &str) -> String {
311        match std::env::var("ARROW_TEST_DATA") {
312            Ok(dir) => format!("{dir}/{path}"),
313            Err(_) => format!("../testing/data/{path}"),
314        }
315    }
316}