This is an automated email from the ASF dual-hosted git repository.
tustvold pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/master by this push:
new 48cc8beaf Split out arrow-schema (#2594) (#2711)
48cc8beaf is described below
commit 48cc8beaf029dec15b72fd70579d188049c79cd1
Author: Raphael Taylor-Davies <[email protected]>
AuthorDate: Wed Sep 21 13:04:52 2022 +0100
Split out arrow-schema (#2594) (#2711)
* Split out arrow-schema (#2594)
* Flatten schema
* Move decimal logic
* Fix doc
* Fix tests
* Fix integration-test
* Remove pyarrow orphan
* PyArrow fixes
* Move ArrowError to arrow-schema
* Fix pyarrow
* Fix test
* Fix conflicts
* Fix pyarrow
* Tweak feature flags
* Test juggling
* Derive PyArrowConvert for Vec
---
.github/workflows/arrow.yml | 2 +
Cargo.toml | 1 +
arrow-pyarrow-integration-testing/src/lib.rs | 45 +-
arrow-schema/Cargo.toml | 47 ++
arrow-schema/src/datatype.rs | 492 +++++++++++++++++
{arrow => arrow-schema}/src/error.rs | 34 +-
{arrow/src/datatypes => arrow-schema/src}/field.rs | 14 +-
arrow/src/csv/mod.rs => arrow-schema/src/lib.rs | 18 +-
.../datatypes/mod.rs => arrow-schema/src/schema.rs | 606 ++++++++++++++-------
arrow/Cargo.toml | 7 +-
arrow/src/csv/mod.rs | 19 +
arrow/src/csv/reader.rs | 7 +-
arrow/src/csv/writer.rs | 8 +-
arrow/src/datatypes/{datatype.rs => decimal.rs} | 382 +------------
arrow/src/datatypes/mod.rs | 557 +------------------
arrow/src/datatypes/schema.rs | 386 -------------
arrow/src/error.rs | 115 +---
arrow/src/json/mod.rs | 33 ++
arrow/src/json/writer.rs | 5 +-
arrow/src/pyarrow.rs | 83 +--
integration-testing/src/util/mod.rs | 3 +-
21 files changed, 1120 insertions(+), 1744 deletions(-)
diff --git a/.github/workflows/arrow.yml b/.github/workflows/arrow.yml
index cdd87ca16..797f63b5a 100644
--- a/.github/workflows/arrow.yml
+++ b/.github/workflows/arrow.yml
@@ -63,6 +63,8 @@ jobs:
cargo run --example read_csv_infer_schema
- name: Run non-archery based integration-tests
run: cargo test -p arrow-integration-testing
+ - name: Test arrow-schema with all features
+ run: cargo test -p arrow-schema --all-features
# test compilaton features
linux-features:
diff --git a/Cargo.toml b/Cargo.toml
index d0233ccb3..355c65a8b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,6 +18,7 @@
[workspace]
members = [
"arrow",
+ "arrow-schema",
"arrow-buffer",
"arrow-flight",
"parquet",
diff --git a/arrow-pyarrow-integration-testing/src/lib.rs
b/arrow-pyarrow-integration-testing/src/lib.rs
index 086b21834..f9e70eb8d 100644
--- a/arrow-pyarrow-integration-testing/src/lib.rs
+++ b/arrow-pyarrow-integration-testing/src/lib.rs
@@ -28,9 +28,13 @@ use arrow::compute::kernels;
use arrow::datatypes::{DataType, Field, Schema};
use arrow::error::ArrowError;
use arrow::ffi_stream::ArrowArrayStreamReader;
-use arrow::pyarrow::PyArrowConvert;
+use arrow::pyarrow::{PyArrowConvert, PyArrowException, PyArrowType};
use arrow::record_batch::RecordBatch;
+fn to_py_err(err: ArrowError) -> PyErr {
+ PyArrowException::new_err(err.to_string())
+}
+
/// Returns `array + array` of an int64 array.
#[pyfunction]
fn double(array: &PyAny, py: Python) -> PyResult<PyObject> {
@@ -41,8 +45,10 @@ fn double(array: &PyAny, py: Python) -> PyResult<PyObject> {
let array = array
.as_any()
.downcast_ref::<Int64Array>()
- .ok_or(ArrowError::ParseError("Expects an int64".to_string()))?;
- let array = kernels::arithmetic::add(array, array)?;
+ .ok_or_else(|| ArrowError::ParseError("Expects an int64".to_string()))
+ .map_err(to_py_err)?;
+
+ let array = kernels::arithmetic::add(array, array).map_err(to_py_err)?;
// export
array.to_pyarrow(py)
@@ -66,56 +72,61 @@ fn double_py(lambda: &PyAny, py: Python) -> PyResult<bool> {
/// Returns the substring
#[pyfunction]
-fn substring(array: ArrayData, start: i64) -> PyResult<ArrayData> {
+fn substring(
+ array: PyArrowType<ArrayData>,
+ start: i64,
+) -> PyResult<PyArrowType<ArrayData>> {
// import
- let array = ArrayRef::from(array);
+ let array = ArrayRef::from(array.0);
// substring
- let array = kernels::substring::substring(array.as_ref(), start, None)?;
+ let array = kernels::substring::substring(array.as_ref(), start,
None).map_err(to_py_err)?;
- Ok(array.data().to_owned())
+ Ok(array.data().to_owned().into())
}
/// Returns the concatenate
#[pyfunction]
-fn concatenate(array: ArrayData, py: Python) -> PyResult<PyObject> {
- let array = ArrayRef::from(array);
+fn concatenate(array: PyArrowType<ArrayData>, py: Python) ->
PyResult<PyObject> {
+ let array = ArrayRef::from(array.0);
// concat
- let array = kernels::concat::concat(&[array.as_ref(), array.as_ref()])?;
+ let array = kernels::concat::concat(&[array.as_ref(),
array.as_ref()]).map_err(to_py_err)?;
array.to_pyarrow(py)
}
#[pyfunction]
-fn round_trip_type(obj: DataType) -> PyResult<DataType> {
+fn round_trip_type(obj: PyArrowType<DataType>) ->
PyResult<PyArrowType<DataType>> {
Ok(obj)
}
#[pyfunction]
-fn round_trip_field(obj: Field) -> PyResult<Field> {
+fn round_trip_field(obj: PyArrowType<Field>) -> PyResult<PyArrowType<Field>> {
Ok(obj)
}
#[pyfunction]
-fn round_trip_schema(obj: Schema) -> PyResult<Schema> {
+fn round_trip_schema(obj: PyArrowType<Schema>) ->
PyResult<PyArrowType<Schema>> {
Ok(obj)
}
#[pyfunction]
-fn round_trip_array(obj: ArrayData) -> PyResult<ArrayData> {
+fn round_trip_array(obj: PyArrowType<ArrayData>) ->
PyResult<PyArrowType<ArrayData>> {
Ok(obj)
}
#[pyfunction]
-fn round_trip_record_batch(obj: RecordBatch) -> PyResult<RecordBatch> {
+fn round_trip_record_batch(
+ obj: PyArrowType<RecordBatch>,
+) -> PyResult<PyArrowType<RecordBatch>> {
Ok(obj)
}
#[pyfunction]
fn round_trip_record_batch_reader(
- obj: ArrowArrayStreamReader,
-) -> PyResult<ArrowArrayStreamReader> {
+ obj: PyArrowType<ArrowArrayStreamReader>,
+) -> PyResult<PyArrowType<ArrowArrayStreamReader>> {
Ok(obj)
}
diff --git a/arrow-schema/Cargo.toml b/arrow-schema/Cargo.toml
new file mode 100644
index 000000000..d35a99a6d
--- /dev/null
+++ b/arrow-schema/Cargo.toml
@@ -0,0 +1,47 @@
+# 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.
+
+[package]
+name = "arrow-schema"
+version = "23.0.0"
+description = "Defines the logical types for arrow arrays"
+homepage = "https://github.com/apache/arrow-rs"
+repository = "https://github.com/apache/arrow-rs"
+authors = ["Apache Arrow <[email protected]>"]
+license = "Apache-2.0"
+keywords = ["arrow"]
+include = [
+ "benches/*.rs",
+ "src/**/*.rs",
+ "Cargo.toml",
+]
+edition = "2021"
+rust-version = "1.62"
+
+[lib]
+name = "arrow_schema"
+path = "src/lib.rs"
+bench = false
+
+[dependencies]
+serde = { version = "1.0", default-features = false, features = ["derive",
"std"], optional = true }
+
+[features]
+default = []
+
+[dev-dependencies]
+serde_json = "1.0"
diff --git a/arrow-schema/src/datatype.rs b/arrow-schema/src/datatype.rs
new file mode 100644
index 000000000..9037f7c9a
--- /dev/null
+++ b/arrow-schema/src/datatype.rs
@@ -0,0 +1,492 @@
+// 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.
+
+use std::fmt;
+
+use crate::field::Field;
+
+/// The set of datatypes that are supported by this implementation of Apache
Arrow.
+///
+/// The Arrow specification on data types includes some more types.
+/// See also
[`Schema.fbs`](https://github.com/apache/arrow/blob/master/format/Schema.fbs)
+/// for Arrow's specification.
+///
+/// The variants of this enum include primitive fixed size types as well as
parametric or
+/// nested types.
+/// Currently the Rust implementation supports the following nested types:
+/// - `List<T>`
+/// - `Struct<T, U, V, ...>`
+///
+/// Nested types can themselves be nested within other arrays.
+/// For more information on these types please see
+/// [the physical memory layout of Apache
Arrow](https://arrow.apache.org/docs/format/Columnar.html#physical-memory-layout).
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub enum DataType {
+ /// Null type
+ Null,
+ /// A boolean datatype representing the values `true` and `false`.
+ Boolean,
+ /// A signed 8-bit integer.
+ Int8,
+ /// A signed 16-bit integer.
+ Int16,
+ /// A signed 32-bit integer.
+ Int32,
+ /// A signed 64-bit integer.
+ Int64,
+ /// An unsigned 8-bit integer.
+ UInt8,
+ /// An unsigned 16-bit integer.
+ UInt16,
+ /// An unsigned 32-bit integer.
+ UInt32,
+ /// An unsigned 64-bit integer.
+ UInt64,
+ /// A 16-bit floating point number.
+ Float16,
+ /// A 32-bit floating point number.
+ Float32,
+ /// A 64-bit floating point number.
+ Float64,
+ /// A timestamp with an optional timezone.
+ ///
+ /// Time is measured as a Unix epoch, counting the seconds from
+ /// 00:00:00.000 on 1 January 1970, excluding leap seconds,
+ /// as a 64-bit integer.
+ ///
+ /// The time zone is a string indicating the name of a time zone, one of:
+ ///
+ /// * As used in the Olson time zone database (the "tz database" or
+ /// "tzdata"), such as "America/New_York"
+ /// * An absolute time zone offset of the form +XX:XX or -XX:XX, such as
+07:30
+ ///
+ /// Timestamps with a non-empty timezone
+ /// ------------------------------------
+ ///
+ /// If a Timestamp column has a non-empty timezone value, its epoch is
+ /// 1970-01-01 00:00:00 (January 1st 1970, midnight) in the *UTC* timezone
+ /// (the Unix epoch), regardless of the Timestamp's own timezone.
+ ///
+ /// Therefore, timestamp values with a non-empty timezone correspond to
+ /// physical points in time together with some additional information about
+ /// how the data was obtained and/or how to display it (the timezone).
+ ///
+ /// For example, the timestamp value 0 with the timezone string
"Europe/Paris"
+ /// corresponds to "January 1st 1970, 00h00" in the UTC timezone, but the
+ /// application may prefer to display it as "January 1st 1970, 01h00" in
+ /// the Europe/Paris timezone (which is the same physical point in time).
+ ///
+ /// One consequence is that timestamp values with a non-empty timezone
+ /// can be compared and ordered directly, since they all share the same
+ /// well-known point of reference (the Unix epoch).
+ ///
+ /// Timestamps with an unset / empty timezone
+ /// -----------------------------------------
+ ///
+ /// If a Timestamp column has no timezone value, its epoch is
+ /// 1970-01-01 00:00:00 (January 1st 1970, midnight) in an *unknown*
timezone.
+ ///
+ /// Therefore, timestamp values without a timezone cannot be meaningfully
+ /// interpreted as physical points in time, but only as calendar / clock
+ /// indications ("wall clock time") in an unspecified timezone.
+ ///
+ /// For example, the timestamp value 0 with an empty timezone string
+ /// corresponds to "January 1st 1970, 00h00" in an unknown timezone:
there
+ /// is not enough information to interpret it as a well-defined physical
+ /// point in time.
+ ///
+ /// One consequence is that timestamp values without a timezone cannot
+ /// be reliably compared or ordered, since they may have different points
of
+ /// reference. In particular, it is *not* possible to interpret an unset
+ /// or empty timezone as the same as "UTC".
+ ///
+ /// Conversion between timezones
+ /// ----------------------------
+ ///
+ /// If a Timestamp column has a non-empty timezone, changing the timezone
+ /// to a different non-empty value is a metadata-only operation:
+ /// the timestamp values need not change as their point of reference
remains
+ /// the same (the Unix epoch).
+ ///
+ /// However, if a Timestamp column has no timezone value, changing it to a
+ /// non-empty value requires to think about the desired semantics.
+ /// One possibility is to assume that the original timestamp values are
+ /// relative to the epoch of the timezone being set; timestamp values
should
+ /// then adjusted to the Unix epoch (for example, changing the timezone
from
+ /// empty to "Europe/Paris" would require converting the timestamp values
+ /// from "Europe/Paris" to "UTC", which seems counter-intuitive but is
+ /// nevertheless correct).
+ Timestamp(TimeUnit, Option<String>),
+ /// A 32-bit date representing the elapsed time since UNIX epoch
(1970-01-01)
+ /// in days (32 bits).
+ Date32,
+ /// A 64-bit date representing the elapsed time since UNIX epoch
(1970-01-01)
+ /// in milliseconds (64 bits). Values are evenly divisible by 86400000.
+ Date64,
+ /// A 32-bit time representing the elapsed time since midnight in the unit
of `TimeUnit`.
+ Time32(TimeUnit),
+ /// A 64-bit time representing the elapsed time since midnight in the unit
of `TimeUnit`.
+ Time64(TimeUnit),
+ /// Measure of elapsed time in either seconds, milliseconds, microseconds
or nanoseconds.
+ Duration(TimeUnit),
+ /// A "calendar" interval which models types that don't necessarily
+ /// have a precise duration without the context of a base timestamp (e.g.
+ /// days can differ in length during day light savings time transitions).
+ Interval(IntervalUnit),
+ /// Opaque binary data of variable length.
+ Binary,
+ /// Opaque binary data of fixed size.
+ /// Enum parameter specifies the number of bytes per value.
+ FixedSizeBinary(i32),
+ /// Opaque binary data of variable length and 64-bit offsets.
+ LargeBinary,
+ /// A variable-length string in Unicode with UTF-8 encoding.
+ Utf8,
+ /// A variable-length string in Unicode with UFT-8 encoding and 64-bit
offsets.
+ LargeUtf8,
+ /// A list of some logical data type with variable length.
+ List(Box<Field>),
+ /// A list of some logical data type with fixed length.
+ FixedSizeList(Box<Field>, i32),
+ /// A list of some logical data type with variable length and 64-bit
offsets.
+ LargeList(Box<Field>),
+ /// A nested datatype that contains a number of sub-fields.
+ Struct(Vec<Field>),
+ /// A nested datatype that can represent slots of differing types.
Components:
+ ///
+ /// 1. [`Field`] for each possible child type the Union can hold
+ /// 2. The corresponding `type_id` used to identify which Field
+ /// 3. The type of union (Sparse or Dense)
+ Union(Vec<Field>, Vec<i8>, UnionMode),
+ /// A dictionary encoded array (`key_type`, `value_type`), where
+ /// each array element is an index of `key_type` into an
+ /// associated dictionary of `value_type`.
+ ///
+ /// Dictionary arrays are used to store columns of `value_type`
+ /// that contain many repeated values using less memory, but with
+ /// a higher CPU overhead for some operations.
+ ///
+ /// This type mostly used to represent low cardinality string
+ /// arrays or a limited set of primitive types as integers.
+ Dictionary(Box<DataType>, Box<DataType>),
+ /// Exact 128-bit width decimal value with precision and scale
+ ///
+ /// * precision is the total number of digits
+ /// * scale is the number of digits past the decimal
+ ///
+ /// For example the number 123.45 has precision 5 and scale 2.
+ Decimal128(u8, u8),
+ /// Exact 256-bit width decimal value with precision and scale
+ ///
+ /// * precision is the total number of digits
+ /// * scale is the number of digits past the decimal
+ ///
+ /// For example the number 123.45 has precision 5 and scale 2.
+ Decimal256(u8, u8),
+ /// A Map is a logical nested type that is represented as
+ ///
+ /// `List<entries: Struct<key: K, value: V>>`
+ ///
+ /// The keys and values are each respectively contiguous.
+ /// The key and value types are not constrained, but keys should be
+ /// hashable and unique.
+ /// Whether the keys are sorted can be set in the `bool` after the `Field`.
+ ///
+ /// In a field with Map type, the field has a child Struct field, which
then
+ /// has two children: key type and the second the value type. The names of
the
+ /// child fields may be respectively "entries", "key", and "value", but
this is
+ /// not enforced.
+ Map(Box<Field>, bool),
+}
+
+/// An absolute length of time in seconds, milliseconds, microseconds or
nanoseconds.
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub enum TimeUnit {
+ /// Time in seconds.
+ Second,
+ /// Time in milliseconds.
+ Millisecond,
+ /// Time in microseconds.
+ Microsecond,
+ /// Time in nanoseconds.
+ Nanosecond,
+}
+
+/// YEAR_MONTH, DAY_TIME, MONTH_DAY_NANO interval in SQL style.
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub enum IntervalUnit {
+ /// Indicates the number of elapsed whole months, stored as 4-byte
integers.
+ YearMonth,
+ /// Indicates the number of elapsed days and milliseconds,
+ /// stored as 2 contiguous 32-bit integers (days, milliseconds) (8-bytes
in total).
+ DayTime,
+ /// A triple of the number of elapsed months, days, and nanoseconds.
+ /// The values are stored contiguously in 16 byte blocks. Months and
+ /// days are encoded as 32 bit integers and nanoseconds is encoded as a
+ /// 64 bit integer. All integers are signed. Each field is independent
+ /// (e.g. there is no constraint that nanoseconds have the same sign
+ /// as days or that the quantity of nanoseconds represents less
+ /// than a day's worth of time).
+ MonthDayNano,
+}
+
+// Sparse or Dense union layouts
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub enum UnionMode {
+ Sparse,
+ Dense,
+}
+
+impl fmt::Display for DataType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl DataType {
+ /// Returns true if the type is primitive: (numeric, temporal).
+ pub fn is_primitive(t: &DataType) -> bool {
+ use DataType::*;
+ matches!(
+ t,
+ Int8 | Int16
+ | Int32
+ | Int64
+ | UInt8
+ | UInt16
+ | UInt32
+ | UInt64
+ | Float32
+ | Float64
+ | Date32
+ | Date64
+ | Time32(_)
+ | Time64(_)
+ | Timestamp(_, _)
+ | Interval(_)
+ | Duration(_)
+ )
+ }
+
+ /// Returns true if this type is numeric: (UInt*, Int*, or Float*).
+ pub fn is_numeric(t: &DataType) -> bool {
+ use DataType::*;
+ matches!(
+ t,
+ UInt8
+ | UInt16
+ | UInt32
+ | UInt64
+ | Int8
+ | Int16
+ | Int32
+ | Int64
+ | Float32
+ | Float64
+ )
+ }
+
+ /// Returns true if this type is temporal: (Date*, Time*, Duration, or
Interval).
+ pub fn is_temporal(t: &DataType) -> bool {
+ use DataType::*;
+ matches!(
+ t,
+ Date32
+ | Date64
+ | Timestamp(_, _)
+ | Time32(_)
+ | Time64(_)
+ | Duration(_)
+ | Interval(_)
+ )
+ }
+
+ /// Returns true if this type is valid as a dictionary key
+ pub fn is_dictionary_key_type(t: &DataType) -> bool {
+ use DataType::*;
+ matches!(
+ t,
+ UInt8 | UInt16 | UInt32 | UInt64 | Int8 | Int16 | Int32 | Int64
+ )
+ }
+
+ /// Returns true if this type is nested (List, FixedSizeList, LargeList,
Struct, Union, or Map)
+ pub fn is_nested(t: &DataType) -> bool {
+ use DataType::*;
+ matches!(
+ t,
+ List(_)
+ | FixedSizeList(_, _)
+ | LargeList(_)
+ | Struct(_)
+ | Union(_, _, _)
+ | Map(_, _)
+ )
+ }
+
+ /// Compares the datatype with another, ignoring nested field names
+ /// and metadata.
+ pub fn equals_datatype(&self, other: &DataType) -> bool {
+ match (&self, other) {
+ (DataType::List(a), DataType::List(b))
+ | (DataType::LargeList(a), DataType::LargeList(b)) => {
+ a.is_nullable() == b.is_nullable()
+ && a.data_type().equals_datatype(b.data_type())
+ }
+ (DataType::FixedSizeList(a, a_size), DataType::FixedSizeList(b,
b_size)) => {
+ a_size == b_size
+ && a.is_nullable() == b.is_nullable()
+ && a.data_type().equals_datatype(b.data_type())
+ }
+ (DataType::Struct(a), DataType::Struct(b)) => {
+ a.len() == b.len()
+ && a.iter().zip(b).all(|(a, b)| {
+ a.is_nullable() == b.is_nullable()
+ && a.data_type().equals_datatype(b.data_type())
+ })
+ }
+ (
+ DataType::Map(a_field, a_is_sorted),
+ DataType::Map(b_field, b_is_sorted),
+ ) => a_field == b_field && a_is_sorted == b_is_sorted,
+ _ => self == other,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ #[cfg(feature = "serde")]
+ fn serde_struct_type() {
+ use std::collections::BTreeMap;
+
+ let kv_array = [("k".to_string(), "v".to_string())];
+ let field_metadata: BTreeMap<String, String> =
kv_array.iter().cloned().collect();
+
+ // Non-empty map: should be converted as JSON obj { ... }
+ let first_name = Field::new("first_name", DataType::Utf8, false)
+ .with_metadata(Some(field_metadata));
+
+ // Empty map: should be omitted.
+ let last_name = Field::new("last_name", DataType::Utf8, false)
+ .with_metadata(Some(BTreeMap::default()));
+
+ let person = DataType::Struct(vec![
+ first_name,
+ last_name,
+ Field::new(
+ "address",
+ DataType::Struct(vec![
+ Field::new("street", DataType::Utf8, false),
+ Field::new("zip", DataType::UInt16, false),
+ ]),
+ false,
+ ),
+ ]);
+
+ let serialized = serde_json::to_string(&person).unwrap();
+
+ // NOTE that this is testing the default (derived) serialization
format, not the
+ // JSON format specified in metadata.md
+
+ assert_eq!(
+ "{\"Struct\":[\
+
{\"name\":\"first_name\",\"data_type\":\"Utf8\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false,\"metadata\":{\"k\":\"v\"}},\
+
{\"name\":\"last_name\",\"data_type\":\"Utf8\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false},\
+ {\"name\":\"address\",\"data_type\":{\"Struct\":\
+
[{\"name\":\"street\",\"data_type\":\"Utf8\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false},\
+
{\"name\":\"zip\",\"data_type\":\"UInt16\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false}\
+ ]},\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false}]}",
+ serialized
+ );
+
+ let deserialized = serde_json::from_str(&serialized).unwrap();
+
+ assert_eq!(person, deserialized);
+ }
+
+ #[test]
+ fn test_list_datatype_equality() {
+ // tests that list type equality is checked while ignoring list names
+ let list_a = DataType::List(Box::new(Field::new("item",
DataType::Int32, true)));
+ let list_b = DataType::List(Box::new(Field::new("array",
DataType::Int32, true)));
+ let list_c = DataType::List(Box::new(Field::new("item",
DataType::Int32, false)));
+ let list_d = DataType::List(Box::new(Field::new("item",
DataType::UInt32, true)));
+ assert!(list_a.equals_datatype(&list_b));
+ assert!(!list_a.equals_datatype(&list_c));
+ assert!(!list_b.equals_datatype(&list_c));
+ assert!(!list_a.equals_datatype(&list_d));
+
+ let list_e =
+ DataType::FixedSizeList(Box::new(Field::new("item", list_a,
false)), 3);
+ let list_f =
+ DataType::FixedSizeList(Box::new(Field::new("array", list_b,
false)), 3);
+ let list_g = DataType::FixedSizeList(
+ Box::new(Field::new("item", DataType::FixedSizeBinary(3), true)),
+ 3,
+ );
+ assert!(list_e.equals_datatype(&list_f));
+ assert!(!list_e.equals_datatype(&list_g));
+ assert!(!list_f.equals_datatype(&list_g));
+
+ let list_h = DataType::Struct(vec![Field::new("f1", list_e, true)]);
+ let list_i = DataType::Struct(vec![Field::new("f1", list_f.clone(),
true)]);
+ let list_j = DataType::Struct(vec![Field::new("f1", list_f.clone(),
false)]);
+ let list_k = DataType::Struct(vec![
+ Field::new("f1", list_f.clone(), false),
+ Field::new("f2", list_g.clone(), false),
+ Field::new("f3", DataType::Utf8, true),
+ ]);
+ let list_l = DataType::Struct(vec![
+ Field::new("ff1", list_f.clone(), false),
+ Field::new("ff2", list_g.clone(), false),
+ Field::new("ff3", DataType::LargeUtf8, true),
+ ]);
+ let list_m = DataType::Struct(vec![
+ Field::new("ff1", list_f, false),
+ Field::new("ff2", list_g, false),
+ Field::new("ff3", DataType::Utf8, true),
+ ]);
+ assert!(list_h.equals_datatype(&list_i));
+ assert!(!list_h.equals_datatype(&list_j));
+ assert!(!list_k.equals_datatype(&list_l));
+ assert!(list_k.equals_datatype(&list_m));
+ }
+
+ #[test]
+ fn create_struct_type() {
+ let _person = DataType::Struct(vec![
+ Field::new("first_name", DataType::Utf8, false),
+ Field::new("last_name", DataType::Utf8, false),
+ Field::new(
+ "address",
+ DataType::Struct(vec![
+ Field::new("street", DataType::Utf8, false),
+ Field::new("zip", DataType::UInt16, false),
+ ]),
+ false,
+ ),
+ ]);
+ }
+}
diff --git a/arrow/src/error.rs b/arrow-schema/src/error.rs
similarity index 76%
copy from arrow/src/error.rs
copy to arrow-schema/src/error.rs
index 5d92fb930..105d4d5e2 100644
--- a/arrow/src/error.rs
+++ b/arrow-schema/src/error.rs
@@ -45,9 +45,7 @@ pub enum ArrowError {
impl ArrowError {
/// Wraps an external error in an `ArrowError`.
- pub fn from_external_error(
- error: Box<dyn ::std::error::Error + Send + Sync>,
- ) -> Self {
+ pub fn from_external_error(error: Box<dyn Error + Send + Sync>) -> Self {
Self::ExternalError(error)
}
}
@@ -58,40 +56,12 @@ impl From<::std::io::Error> for ArrowError {
}
}
-#[cfg(feature = "csv")]
-impl From<csv_crate::Error> for ArrowError {
- fn from(error: csv_crate::Error) -> Self {
- match error.kind() {
- csv_crate::ErrorKind::Io(error) =>
ArrowError::CsvError(error.to_string()),
- csv_crate::ErrorKind::Utf8 { pos: _, err } =>
ArrowError::CsvError(format!(
- "Encountered UTF-8 error while reading CSV file: {}",
- err
- )),
- csv_crate::ErrorKind::UnequalLengths {
- expected_len, len, ..
- } => ArrowError::CsvError(format!(
- "Encountered unequal lengths between records on CSV file.
Expected {} \
- records, found {} records",
- len, expected_len
- )),
- _ => ArrowError::CsvError("Error reading CSV file".to_string()),
- }
- }
-}
-
impl From<::std::string::FromUtf8Error> for ArrowError {
fn from(error: std::string::FromUtf8Error) -> Self {
ArrowError::ParseError(error.to_string())
}
}
-#[cfg(feature = "json")]
-impl From<serde_json::Error> for ArrowError {
- fn from(error: serde_json::Error) -> Self {
- ArrowError::JsonError(error.to_string())
- }
-}
-
impl<W: Write> From<::std::io::IntoInnerError<W>> for ArrowError {
fn from(error: std::io::IntoInnerError<W>) -> Self {
ArrowError::IoError(error.to_string())
@@ -131,5 +101,3 @@ impl Display for ArrowError {
}
impl Error for ArrowError {}
-
-pub type Result<T> = std::result::Result<T, ArrowError>;
diff --git a/arrow/src/datatypes/field.rs b/arrow-schema/src/field.rs
similarity index 98%
rename from arrow/src/datatypes/field.rs
rename to arrow-schema/src/field.rs
index 03d078077..adafbfa9b 100644
--- a/arrow/src/datatypes/field.rs
+++ b/arrow-schema/src/field.rs
@@ -15,12 +15,12 @@
// specific language governing permissions and limitations
// under the License.
-use crate::error::{ArrowError, Result};
+use crate::error::ArrowError;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::hash::{Hash, Hasher};
-use super::DataType;
+use crate::datatype::DataType;
/// Describes a single column in a [`Schema`](super::Schema).
///
@@ -145,7 +145,7 @@ impl Field {
/// Set the name of the [`Field`] and returns self.
///
/// ```
- /// # use arrow::datatypes::*;
+ /// # use arrow_schema::*;
/// let field = Field::new("c1", DataType::Int64, false)
/// .with_name("c2");
///
@@ -165,7 +165,7 @@ impl Field {
/// Set [`DataType`] of the [`Field`] and returns self.
///
/// ```
- /// # use arrow::datatypes::*;
+ /// # use arrow_schema::*;
/// let field = Field::new("c1", DataType::Int64, false)
/// .with_data_type(DataType::Utf8);
///
@@ -185,7 +185,7 @@ impl Field {
/// Set `nullable` of the [`Field`] and returns self.
///
/// ```
- /// # use arrow::datatypes::*;
+ /// # use arrow_schema::*;
/// let field = Field::new("c1", DataType::Int64, false)
/// .with_nullable(true);
///
@@ -259,12 +259,12 @@ impl Field {
/// Example:
///
/// ```
- /// # use arrow::datatypes::*;
+ /// # use arrow_schema::*;
/// let mut field = Field::new("c1", DataType::Int64, false);
/// assert!(field.try_merge(&Field::new("c1", DataType::Int64,
true)).is_ok());
/// assert!(field.is_nullable());
/// ```
- pub fn try_merge(&mut self, from: &Field) -> Result<()> {
+ pub fn try_merge(&mut self, from: &Field) -> Result<(), ArrowError> {
if from.dict_id != self.dict_id {
return Err(ArrowError::SchemaError(
"Fail to merge schema Field due to conflicting
dict_id".to_string(),
diff --git a/arrow/src/csv/mod.rs b/arrow-schema/src/lib.rs
similarity index 72%
copy from arrow/src/csv/mod.rs
copy to arrow-schema/src/lib.rs
index ffe82f335..34030f2d3 100644
--- a/arrow/src/csv/mod.rs
+++ b/arrow-schema/src/lib.rs
@@ -15,13 +15,13 @@
// specific language governing permissions and limitations
// under the License.
-//! Transfer data between the Arrow memory format and CSV (comma-separated
values).
+//! Arrow logical types
-pub mod reader;
-pub mod writer;
-
-pub use self::reader::infer_schema_from_files;
-pub use self::reader::Reader;
-pub use self::reader::ReaderBuilder;
-pub use self::writer::Writer;
-pub use self::writer::WriterBuilder;
+mod datatype;
+pub use datatype::*;
+mod error;
+pub use error::*;
+mod field;
+pub use field::*;
+mod schema;
+pub use schema::*;
diff --git a/arrow/src/datatypes/mod.rs b/arrow-schema/src/schema.rs
similarity index 52%
copy from arrow/src/datatypes/mod.rs
copy to arrow-schema/src/schema.rs
index 1586d563c..9605cdda7 100644
--- a/arrow/src/datatypes/mod.rs
+++ b/arrow-schema/src/schema.rs
@@ -15,170 +15,426 @@
// specific language governing permissions and limitations
// under the License.
-//! Defines the logical data types of Arrow arrays.
-//!
-//! The most important things you might be looking for are:
-//! * [`Schema`](crate::datatypes::Schema) to describe a schema.
-//! * [`Field`](crate::datatypes::Field) to describe one field within a
schema.
-//! * [`DataType`](crate::datatypes::DataType) to describe the type of a
field.
-
-use std::sync::Arc;
-
-mod native;
-pub use native::*;
-mod field;
-pub use field::*;
-mod schema;
-pub use schema::*;
-mod numeric;
-pub use numeric::*;
-mod types;
-pub use types::*;
-mod datatype;
-pub use datatype::*;
-mod delta;
-
-#[cfg(feature = "ffi")]
-mod ffi;
-#[cfg(feature = "ffi")]
-pub use ffi::*;
-
-/// A reference-counted reference to a [`Schema`](crate::datatypes::Schema).
-pub type SchemaRef = Arc<Schema>;
+use std::collections::HashMap;
+use std::fmt;
+use std::hash::Hash;
+
+use crate::error::ArrowError;
+use crate::field::Field;
+
+/// Describes the meta-data of an ordered sequence of relative types.
+///
+/// Note that this information is only part of the meta-data and not part of
the physical
+/// memory layout.
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub struct Schema {
+ pub fields: Vec<Field>,
+ /// A map of key-value pairs containing additional meta data.
+ #[cfg_attr(
+ feature = "serde",
+ serde(skip_serializing_if = "HashMap::is_empty", default)
+ )]
+ pub metadata: HashMap<String, String>,
+}
+
+impl Schema {
+ /// Creates an empty `Schema`
+ pub fn empty() -> Self {
+ Self {
+ fields: vec![],
+ metadata: HashMap::new(),
+ }
+ }
+
+ /// Creates a new [`Schema`] from a sequence of [`Field`] values.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use arrow_schema::*;
+ /// let field_a = Field::new("a", DataType::Int64, false);
+ /// let field_b = Field::new("b", DataType::Boolean, false);
+ ///
+ /// let schema = Schema::new(vec![field_a, field_b]);
+ /// ```
+ pub fn new(fields: Vec<Field>) -> Self {
+ Self::new_with_metadata(fields, HashMap::new())
+ }
+
+ /// Creates a new [`Schema`] from a sequence of [`Field`] values
+ /// and adds additional metadata in form of key value pairs.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use arrow_schema::*;
+ /// # use std::collections::HashMap;
+ ///
+ /// let field_a = Field::new("a", DataType::Int64, false);
+ /// let field_b = Field::new("b", DataType::Boolean, false);
+ ///
+ /// let mut metadata: HashMap<String, String> = HashMap::new();
+ /// metadata.insert("row_count".to_string(), "100".to_string());
+ ///
+ /// let schema = Schema::new_with_metadata(vec![field_a, field_b],
metadata);
+ /// ```
+ #[inline]
+ pub const fn new_with_metadata(
+ fields: Vec<Field>,
+ metadata: HashMap<String, String>,
+ ) -> Self {
+ Self { fields, metadata }
+ }
+
+ /// Sets the metadata of this `Schema` to be `metadata` and returns self
+ pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
+ self.metadata = metadata;
+ self
+ }
+
+ /// Returns a new schema with only the specified columns in the new schema
+ /// This carries metadata from the parent schema over as well
+ pub fn project(&self, indices: &[usize]) -> Result<Schema, ArrowError> {
+ let new_fields = indices
+ .iter()
+ .map(|i| {
+ self.fields.get(*i).cloned().ok_or_else(|| {
+ ArrowError::SchemaError(format!(
+ "project index {} out of bounds, max field {}",
+ i,
+ self.fields().len()
+ ))
+ })
+ })
+ .collect::<Result<Vec<_>, _>>()?;
+ Ok(Self::new_with_metadata(new_fields, self.metadata.clone()))
+ }
+
+ /// Merge schema into self if it is compatible. Struct fields will be
merged recursively.
+ ///
+ /// Example:
+ ///
+ /// ```
+ /// # use arrow_schema::*;
+ ///
+ /// let merged = Schema::try_merge(vec![
+ /// Schema::new(vec![
+ /// Field::new("c1", DataType::Int64, false),
+ /// Field::new("c2", DataType::Utf8, false),
+ /// ]),
+ /// Schema::new(vec![
+ /// Field::new("c1", DataType::Int64, true),
+ /// Field::new("c2", DataType::Utf8, false),
+ /// Field::new("c3", DataType::Utf8, false),
+ /// ]),
+ /// ]).unwrap();
+ ///
+ /// assert_eq!(
+ /// merged,
+ /// Schema::new(vec![
+ /// Field::new("c1", DataType::Int64, true),
+ /// Field::new("c2", DataType::Utf8, false),
+ /// Field::new("c3", DataType::Utf8, false),
+ /// ]),
+ /// );
+ /// ```
+ pub fn try_merge(
+ schemas: impl IntoIterator<Item = Self>,
+ ) -> Result<Self, ArrowError> {
+ schemas
+ .into_iter()
+ .try_fold(Self::empty(), |mut merged, schema| {
+ let Schema { metadata, fields } = schema;
+ for (key, value) in metadata.into_iter() {
+ // merge metadata
+ if let Some(old_val) = merged.metadata.get(&key) {
+ if old_val != &value {
+ return Err(ArrowError::SchemaError(format!(
+ "Fail to merge schema due to conflicting
metadata. \
+ Key '{}' has different values '{}'
and '{}'",
+ key, old_val, value
+ )));
+ }
+ }
+ merged.metadata.insert(key, value);
+ }
+ // merge fields
+ for field in fields.into_iter() {
+ let merged_field =
+ merged.fields.iter_mut().find(|f| f.name() ==
field.name());
+ match merged_field {
+ Some(merged_field) => merged_field.try_merge(&field)?,
+ // found a new field, add to field list
+ None => merged.fields.push(field),
+ }
+ }
+ Ok(merged)
+ })
+ }
+
+ /// Returns an immutable reference of the vector of `Field` instances.
+ #[inline]
+ pub const fn fields(&self) -> &Vec<Field> {
+ &self.fields
+ }
+
+ /// Returns a vector with references to all fields (including nested
fields)
+ #[inline]
+ pub fn all_fields(&self) -> Vec<&Field> {
+ self.fields.iter().flat_map(|f| f.fields()).collect()
+ }
+
+ /// Returns an immutable reference of a specific [`Field`] instance
selected using an
+ /// offset within the internal `fields` vector.
+ pub fn field(&self, i: usize) -> &Field {
+ &self.fields[i]
+ }
+
+ /// Returns an immutable reference of a specific [`Field`] instance
selected by name.
+ pub fn field_with_name(&self, name: &str) -> Result<&Field, ArrowError> {
+ Ok(&self.fields[self.index_of(name)?])
+ }
+
+ /// Returns a vector of immutable references to all [`Field`] instances
selected by
+ /// the dictionary ID they use.
+ pub fn fields_with_dict_id(&self, dict_id: i64) -> Vec<&Field> {
+ self.fields
+ .iter()
+ .flat_map(|f| f.fields_with_dict_id(dict_id))
+ .collect()
+ }
+
+ /// Find the index of the column with the given name.
+ pub fn index_of(&self, name: &str) -> Result<usize, ArrowError> {
+ (0..self.fields.len())
+ .find(|idx| self.fields[*idx].name() == name)
+ .ok_or_else(|| {
+ let valid_fields: Vec<String> =
+ self.fields.iter().map(|f| f.name().clone()).collect();
+ ArrowError::SchemaError(format!(
+ "Unable to get field named \"{}\". Valid fields: {:?}",
+ name, valid_fields
+ ))
+ })
+ }
+
+ /// Returns an immutable reference to the Map of custom metadata key-value
pairs.
+ #[inline]
+ pub const fn metadata(&self) -> &HashMap<String, String> {
+ &self.metadata
+ }
+
+ /// Look up a column by name and return a immutable reference to the
column along with
+ /// its index.
+ pub fn column_with_name(&self, name: &str) -> Option<(usize, &Field)> {
+ self.fields
+ .iter()
+ .enumerate()
+ .find(|&(_, c)| c.name() == name)
+ }
+
+ /// Check to see if `self` is a superset of `other` schema. Here are the
comparison rules:
+ ///
+ /// * `self` and `other` should contain the same number of fields
+ /// * for every field `f` in `other`, the field in `self` with
corresponding index should be a
+ /// superset of `f`.
+ /// * self.metadata is a superset of other.metadata
+ ///
+ /// In other words, any record conforms to `other` should also conform to
`self`.
+ pub fn contains(&self, other: &Schema) -> bool {
+ self.fields.len() == other.fields.len()
+ && self.fields.iter().zip(other.fields.iter()).all(|(f1, f2)|
f1.contains(f2))
+ // make sure self.metadata is a superset of other.metadata
+ && other.metadata.iter().all(|(k, v1)| match self.metadata.get(k) {
+ Some(v2) => v1 == v2,
+ _ => false,
+ })
+ }
+}
+
+impl fmt::Display for Schema {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(
+ &self
+ .fields
+ .iter()
+ .map(|c| c.to_string())
+ .collect::<Vec<String>>()
+ .join(", "),
+ )
+ }
+}
+
+// need to implement `Hash` manually because `HashMap` implement Eq but no
`Hash`
+#[allow(clippy::derive_hash_xor_eq)]
+impl Hash for Schema {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.fields.hash(state);
+
+ // ensure deterministic key order
+ let mut keys: Vec<&String> = self.metadata.keys().collect();
+ keys.sort();
+ for k in keys {
+ k.hash(state);
+ self.metadata.get(k).expect("key valid").hash(state);
+ }
+ }
+}
#[cfg(test)]
mod tests {
use super::*;
- use crate::error::Result;
- use std::collections::{BTreeMap, HashMap};
+ use crate::datatype::DataType;
+ use crate::{TimeUnit, UnionMode};
+ use std::collections::BTreeMap;
+
+ #[test]
+ #[cfg(feature = "serde")]
+ fn test_ser_de_metadata() {
+ // ser/de with empty metadata
+ let schema = Schema::new(vec![
+ Field::new("name", DataType::Utf8, false),
+ Field::new("address", DataType::Utf8, false),
+ Field::new("priority", DataType::UInt8, false),
+ ]);
+
+ let json = serde_json::to_string(&schema).unwrap();
+ let de_schema = serde_json::from_str(&json).unwrap();
- #[cfg(feature = "json")]
- use crate::json::JsonSerializable;
+ assert_eq!(schema, de_schema);
- #[cfg(feature = "json")]
- use serde_json::{
- Number,
- Value::{Bool, Number as VNumber, String as VString},
- };
+ // ser/de with non-empty metadata
+ let schema = schema
+ .with_metadata([("key".to_owned(),
"val".to_owned())].into_iter().collect());
+ let json = serde_json::to_string(&schema).unwrap();
+ let de_schema = serde_json::from_str(&json).unwrap();
+
+ assert_eq!(schema, de_schema);
+ }
#[test]
- fn test_list_datatype_equality() {
- // tests that list type equality is checked while ignoring list names
- let list_a = DataType::List(Box::new(Field::new("item",
DataType::Int32, true)));
- let list_b = DataType::List(Box::new(Field::new("array",
DataType::Int32, true)));
- let list_c = DataType::List(Box::new(Field::new("item",
DataType::Int32, false)));
- let list_d = DataType::List(Box::new(Field::new("item",
DataType::UInt32, true)));
- assert!(list_a.equals_datatype(&list_b));
- assert!(!list_a.equals_datatype(&list_c));
- assert!(!list_b.equals_datatype(&list_c));
- assert!(!list_a.equals_datatype(&list_d));
-
- let list_e =
- DataType::FixedSizeList(Box::new(Field::new("item", list_a,
false)), 3);
- let list_f =
- DataType::FixedSizeList(Box::new(Field::new("array", list_b,
false)), 3);
- let list_g = DataType::FixedSizeList(
- Box::new(Field::new("item", DataType::FixedSizeBinary(3), true)),
- 3,
- );
- assert!(list_e.equals_datatype(&list_f));
- assert!(!list_e.equals_datatype(&list_g));
- assert!(!list_f.equals_datatype(&list_g));
-
- let list_h = DataType::Struct(vec![Field::new("f1", list_e, true)]);
- let list_i = DataType::Struct(vec![Field::new("f1", list_f.clone(),
true)]);
- let list_j = DataType::Struct(vec![Field::new("f1", list_f.clone(),
false)]);
- let list_k = DataType::Struct(vec![
- Field::new("f1", list_f.clone(), false),
- Field::new("f2", list_g.clone(), false),
- Field::new("f3", DataType::Utf8, true),
- ]);
- let list_l = DataType::Struct(vec![
- Field::new("ff1", list_f.clone(), false),
- Field::new("ff2", list_g.clone(), false),
- Field::new("ff3", DataType::LargeUtf8, true),
- ]);
- let list_m = DataType::Struct(vec![
- Field::new("ff1", list_f, false),
- Field::new("ff2", list_g, false),
- Field::new("ff3", DataType::Utf8, true),
- ]);
- assert!(list_h.equals_datatype(&list_i));
- assert!(!list_h.equals_datatype(&list_j));
- assert!(!list_k.equals_datatype(&list_l));
- assert!(list_k.equals_datatype(&list_m));
+ fn test_projection() {
+ let mut metadata = HashMap::new();
+ metadata.insert("meta".to_string(), "data".to_string());
+
+ let schema = Schema::new(vec![
+ Field::new("name", DataType::Utf8, false),
+ Field::new("address", DataType::Utf8, false),
+ Field::new("priority", DataType::UInt8, false),
+ ])
+ .with_metadata(metadata);
+
+ let projected: Schema = schema.project(&[0, 2]).unwrap();
+
+ assert_eq!(projected.fields().len(), 2);
+ assert_eq!(projected.fields()[0].name(), "name");
+ assert_eq!(projected.fields()[1].name(), "priority");
+ assert_eq!(projected.metadata.get("meta").unwrap(), "data")
}
#[test]
- #[cfg(feature = "json")]
- fn create_struct_type() {
- let _person = DataType::Struct(vec![
- Field::new("first_name", DataType::Utf8, false),
- Field::new("last_name", DataType::Utf8, false),
- Field::new(
- "address",
- DataType::Struct(vec![
- Field::new("street", DataType::Utf8, false),
- Field::new("zip", DataType::UInt16, false),
- ]),
- false,
- ),
- ]);
+ fn test_oob_projection() {
+ let mut metadata = HashMap::new();
+ metadata.insert("meta".to_string(), "data".to_string());
+
+ let schema = Schema::new(vec![
+ Field::new("name", DataType::Utf8, false),
+ Field::new("address", DataType::Utf8, false),
+ Field::new("priority", DataType::UInt8, false),
+ ])
+ .with_metadata(metadata);
+
+ let projected = schema.project(&[0, 3]);
+
+ assert!(projected.is_err());
+ if let Err(e) = projected {
+ assert_eq!(
+ e.to_string(),
+ "Schema error: project index 3 out of bounds, max field
3".to_string()
+ )
+ }
}
#[test]
- #[cfg(feature = "json")]
- fn serde_struct_type() {
- let kv_array = [("k".to_string(), "v".to_string())];
- let field_metadata: BTreeMap<String, String> =
kv_array.iter().cloned().collect();
+ fn test_schema_contains() {
+ let mut metadata1 = HashMap::new();
+ metadata1.insert("meta".to_string(), "data".to_string());
- // Non-empty map: should be converted as JSON obj { ... }
- let first_name = Field::new("first_name", DataType::Utf8, false)
- .with_metadata(Some(field_metadata));
+ let schema1 = Schema::new(vec![
+ Field::new("name", DataType::Utf8, false),
+ Field::new("address", DataType::Utf8, false),
+ Field::new("priority", DataType::UInt8, false),
+ ])
+ .with_metadata(metadata1.clone());
- // Empty map: should be omitted.
- let last_name = Field::new("last_name", DataType::Utf8, false)
- .with_metadata(Some(BTreeMap::default()));
+ let mut metadata2 = HashMap::new();
+ metadata2.insert("meta".to_string(), "data".to_string());
+ metadata2.insert("meta2".to_string(), "data".to_string());
+ let schema2 = Schema::new(vec![
+ Field::new("name", DataType::Utf8, false),
+ Field::new("address", DataType::Utf8, false),
+ Field::new("priority", DataType::UInt8, false),
+ ])
+ .with_metadata(metadata2);
- let person = DataType::Struct(vec![
- first_name,
- last_name,
- Field::new(
- "address",
- DataType::Struct(vec![
- Field::new("street", DataType::Utf8, false),
- Field::new("zip", DataType::UInt16, false),
- ]),
- false,
- ),
- ]);
+ // reflexivity
+ assert!(schema1.contains(&schema1));
+ assert!(schema2.contains(&schema2));
- let serialized = serde_json::to_string(&person).unwrap();
+ assert!(!schema1.contains(&schema2));
+ assert!(schema2.contains(&schema1));
+ }
- // NOTE that this is testing the default (derived) serialization
format, not the
- // JSON format specified in metadata.md
+ #[test]
+ fn schema_equality() {
+ let schema1 = Schema::new(vec![
+ Field::new("c1", DataType::Utf8, false),
+ Field::new("c2", DataType::Float64, true),
+ Field::new("c3", DataType::LargeBinary, true),
+ ]);
+ let schema2 = Schema::new(vec![
+ Field::new("c1", DataType::Utf8, false),
+ Field::new("c2", DataType::Float64, true),
+ Field::new("c3", DataType::LargeBinary, true),
+ ]);
- assert_eq!(
- "{\"Struct\":[\
-
{\"name\":\"first_name\",\"data_type\":\"Utf8\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false,\"metadata\":{\"k\":\"v\"}},\
-
{\"name\":\"last_name\",\"data_type\":\"Utf8\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false},\
- {\"name\":\"address\",\"data_type\":{\"Struct\":\
-
[{\"name\":\"street\",\"data_type\":\"Utf8\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false},\
-
{\"name\":\"zip\",\"data_type\":\"UInt16\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false}\
- ]},\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false}]}",
- serialized
- );
+ assert_eq!(schema1, schema2);
- let deserialized = serde_json::from_str(&serialized).unwrap();
+ let schema3 = Schema::new(vec![
+ Field::new("c1", DataType::Utf8, false),
+ Field::new("c2", DataType::Float32, true),
+ ]);
+ let schema4 = Schema::new(vec![
+ Field::new("C1", DataType::Utf8, false),
+ Field::new("C2", DataType::Float64, true),
+ ]);
+
+ assert_ne!(schema1, schema3);
+ assert_ne!(schema1, schema4);
+ assert_ne!(schema2, schema3);
+ assert_ne!(schema2, schema4);
+ assert_ne!(schema3, schema4);
- assert_eq!(person, deserialized);
+ let f = Field::new("c1", DataType::Utf8, false).with_metadata(Some(
+ [("foo".to_string(), "bar".to_string())]
+ .iter()
+ .cloned()
+ .collect(),
+ ));
+ let schema5 = Schema::new(vec![
+ f,
+ Field::new("c2", DataType::Float64, true),
+ Field::new("c3", DataType::LargeBinary, true),
+ ]);
+ assert_ne!(schema1, schema5);
}
#[test]
fn create_schema_string() {
let schema = person_schema();
assert_eq!(schema.to_string(),
- "Field { name: \"first_name\", data_type: Utf8, nullable: false,
dict_id: 0, dict_is_ordered: false, metadata: Some({\"k\": \"v\"}) }, \
+ "Field { name: \"first_name\", data_type: Utf8, nullable:
false, dict_id: 0, dict_is_ordered: false, metadata: Some({\"k\": \"v\"}) }, \
Field { name: \"last_name\", data_type: Utf8, nullable: false,
dict_id: 0, dict_is_ordered: false, metadata: None }, \
Field { name: \"address\", data_type: Struct([\
Field { name: \"street\", data_type: Utf8, nullable: false,
dict_id: 0, dict_is_ordered: false, metadata: None }, \
@@ -262,74 +518,6 @@ mod tests {
assert!(schema.fields_with_dict_id(456).is_empty());
}
- #[test]
- fn schema_equality() {
- let schema1 = Schema::new(vec![
- Field::new("c1", DataType::Utf8, false),
- Field::new("c2", DataType::Float64, true),
- Field::new("c3", DataType::LargeBinary, true),
- ]);
- let schema2 = Schema::new(vec![
- Field::new("c1", DataType::Utf8, false),
- Field::new("c2", DataType::Float64, true),
- Field::new("c3", DataType::LargeBinary, true),
- ]);
-
- assert_eq!(schema1, schema2);
-
- let schema3 = Schema::new(vec![
- Field::new("c1", DataType::Utf8, false),
- Field::new("c2", DataType::Float32, true),
- ]);
- let schema4 = Schema::new(vec![
- Field::new("C1", DataType::Utf8, false),
- Field::new("C2", DataType::Float64, true),
- ]);
-
- assert!(schema1 != schema3);
- assert!(schema1 != schema4);
- assert!(schema2 != schema3);
- assert!(schema2 != schema4);
- assert!(schema3 != schema4);
-
- let f = Field::new("c1", DataType::Utf8, false).with_metadata(Some(
- [("foo".to_string(), "bar".to_string())]
- .iter()
- .cloned()
- .collect(),
- ));
- let schema5 = Schema::new(vec![
- f,
- Field::new("c2", DataType::Float64, true),
- Field::new("c3", DataType::LargeBinary, true),
- ]);
- assert!(schema1 != schema5);
- }
-
- #[test]
- #[cfg(feature = "json")]
- fn test_arrow_native_type_to_json() {
- assert_eq!(Some(Bool(true)), true.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1i8.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1i16.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1i32.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1i64.into_json_value());
- assert_eq!(Some(VString("1".to_string())), 1i128.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1u8.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1u16.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1u32.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1u64.into_json_value());
- assert_eq!(
- Some(VNumber(Number::from_f64(0.01f64).unwrap())),
- 0.01.into_json_value()
- );
- assert_eq!(
- Some(VNumber(Number::from_f64(0.01f64).unwrap())),
- 0.01f64.into_json_value()
- );
- assert_eq!(None, f32::NAN.into_json_value());
- }
-
fn person_schema() -> Schema {
let kv_array = [("k".to_string(), "v".to_string())];
let field_metadata: BTreeMap<String, String> =
kv_array.iter().cloned().collect();
@@ -451,7 +639,7 @@ mod tests {
}
#[test]
- fn test_schema_merge() -> Result<()> {
+ fn test_schema_merge() {
let merged = Schema::try_merge(vec![
Schema::new(vec![
Field::new("first_name", DataType::Utf8, false),
@@ -484,7 +672,8 @@ mod tests {
.cloned()
.collect::<HashMap<String, String>>(),
),
- ])?;
+ ])
+ .unwrap();
assert_eq!(
merged,
@@ -536,7 +725,8 @@ mod tests {
),
false
),])
- ])?,
+ ])
+ .unwrap(),
Schema::new(vec![Field::new(
"c1",
DataType::Union(
@@ -588,7 +778,5 @@ mod tests {
expected,
res
);
-
- Ok(())
}
}
diff --git a/arrow/Cargo.toml b/arrow/Cargo.toml
index f8dbf1481..d49acef33 100644
--- a/arrow/Cargo.toml
+++ b/arrow/Cargo.toml
@@ -44,9 +44,8 @@ ahash = { version = "0.8", default-features = false, features
= ["compile-time-r
ahash = { version = "0.8", default-features = false, features =
["runtime-rng"] }
[dependencies]
-arrow-buffer = { path = "../arrow-buffer", version = "23.0.0" }
-
-serde = { version = "1.0", default-features = false, features = ["derive"],
optional = true }
+arrow-buffer = { version = "23.0.0", path = "../arrow-buffer" }
+arrow-schema = { version = "23.0.0", path = "../arrow-schema" }
serde_json = { version = "1.0", default-features = false, features = ["std"],
optional = true }
indexmap = { version = "1.9", default-features = false, features = ["std"] }
rand = { version = "0.8", default-features = false, features = ["std",
"std_rng"], optional = true }
@@ -77,7 +76,7 @@ default = ["csv", "ipc", "json"]
ipc_compression = ["ipc", "zstd", "lz4"]
csv = ["csv_crate"]
ipc = ["flatbuffers"]
-json = ["serde", "serde_json"]
+json = ["serde_json"]
simd = ["packed_simd"]
prettyprint = ["comfy-table"]
# The test utils feature enables code used in benchmarks and tests but
diff --git a/arrow/src/csv/mod.rs b/arrow/src/csv/mod.rs
index ffe82f335..46ba7d71e 100644
--- a/arrow/src/csv/mod.rs
+++ b/arrow/src/csv/mod.rs
@@ -25,3 +25,22 @@ pub use self::reader::Reader;
pub use self::reader::ReaderBuilder;
pub use self::writer::Writer;
pub use self::writer::WriterBuilder;
+use arrow_schema::ArrowError;
+
+fn map_csv_error(error: csv_crate::Error) -> ArrowError {
+ match error.kind() {
+ csv_crate::ErrorKind::Io(error) =>
ArrowError::CsvError(error.to_string()),
+ csv_crate::ErrorKind::Utf8 { pos: _, err } =>
ArrowError::CsvError(format!(
+ "Encountered UTF-8 error while reading CSV file: {}",
+ err
+ )),
+ csv_crate::ErrorKind::UnequalLengths {
+ expected_len, len, ..
+ } => ArrowError::CsvError(format!(
+ "Encountered unequal lengths between records on CSV file. Expected
{} \
+ records, found {} records",
+ len, expected_len
+ )),
+ _ => ArrowError::CsvError("Error reading CSV file".to_string()),
+ }
+}
diff --git a/arrow/src/csv/reader.rs b/arrow/src/csv/reader.rs
index d164d35c3..3ec605dd0 100644
--- a/arrow/src/csv/reader.rs
+++ b/arrow/src/csv/reader.rs
@@ -58,6 +58,7 @@ use crate::error::{ArrowError, Result};
use crate::record_batch::{RecordBatch, RecordBatchOptions};
use crate::util::reader_parser::Parser;
+use crate::csv::map_csv_error;
use csv_crate::{ByteRecord, StringRecord};
use std::ops::Neg;
@@ -187,10 +188,10 @@ fn infer_reader_schema_with_csv_options<R: Read>(
// get or create header names
// when has_header is false, creates default column names with column_
prefix
let headers: Vec<String> = if roptions.has_header {
- let headers = &csv_reader.headers()?.clone();
+ let headers = &csv_reader.headers().map_err(map_csv_error)?.clone();
headers.iter().map(|s| s.to_string()).collect()
} else {
- let first_record_count = &csv_reader.headers()?.len();
+ let first_record_count =
&csv_reader.headers().map_err(map_csv_error)?.len();
(0..*first_record_count)
.map(|i| format!("column_{}", i + 1))
.collect()
@@ -208,7 +209,7 @@ fn infer_reader_schema_with_csv_options<R: Read>(
let mut record = StringRecord::new();
let max_records = roptions.max_read_records.unwrap_or(usize::MAX);
while records_count < max_records {
- if !csv_reader.read_record(&mut record)? {
+ if !csv_reader.read_record(&mut record).map_err(map_csv_error)? {
break;
}
records_count += 1;
diff --git a/arrow/src/csv/writer.rs b/arrow/src/csv/writer.rs
index 7097706ba..1b377c38b 100644
--- a/arrow/src/csv/writer.rs
+++ b/arrow/src/csv/writer.rs
@@ -70,11 +70,13 @@ use
crate::compute::kernels::temporal::using_chrono_tz_and_utc_naive_date_time;
#[cfg(feature = "chrono-tz")]
use chrono::{DateTime, Utc};
+use crate::csv::map_csv_error;
use crate::datatypes::*;
use crate::error::{ArrowError, Result};
use crate::record_batch::RecordBatch;
use crate::util::display::make_string_from_decimal;
use crate::{array::*, util::serialization::lexical_to_string};
+
const DEFAULT_DATE_FORMAT: &str = "%F";
const DEFAULT_TIME_FORMAT: &str = "%T";
const DEFAULT_TIMESTAMP_FORMAT: &str = "%FT%H:%M:%S.%9f";
@@ -343,7 +345,9 @@ impl<W: Write> Writer<W> {
.fields()
.iter()
.for_each(|field| headers.push(field.name().to_string()));
- self.writer.write_record(&headers[..])?;
+ self.writer
+ .write_record(&headers[..])
+ .map_err(map_csv_error)?;
}
self.beginning = false;
}
@@ -364,7 +368,7 @@ impl<W: Write> Writer<W> {
for row_index in 0..batch.num_rows() {
self.convert(columns.as_slice(), row_index, &mut buffer)?;
- self.writer.write_record(&buffer)?;
+ self.writer.write_record(&buffer).map_err(map_csv_error)?;
}
self.writer.flush()?;
diff --git a/arrow/src/datatypes/datatype.rs b/arrow/src/datatypes/decimal.rs
similarity index 68%
rename from arrow/src/datatypes/datatype.rs
rename to arrow/src/datatypes/decimal.rs
index d3189b8b1..ffdb04e0d 100644
--- a/arrow/src/datatypes/datatype.rs
+++ b/arrow/src/datatypes/decimal.rs
@@ -15,256 +15,10 @@
// specific language governing permissions and limitations
// under the License.
-use num::BigInt;
-use std::cmp::Ordering;
-use std::fmt;
-
use crate::error::{ArrowError, Result};
use crate::util::decimal::singed_cmp_le_bytes;
-
-use super::Field;
-
-/// The set of datatypes that are supported by this implementation of Apache
Arrow.
-///
-/// The Arrow specification on data types includes some more types.
-/// See also
[`Schema.fbs`](https://github.com/apache/arrow/blob/master/format/Schema.fbs)
-/// for Arrow's specification.
-///
-/// The variants of this enum include primitive fixed size types as well as
parametric or
-/// nested types.
-/// Currently the Rust implementation supports the following nested types:
-/// - `List<T>`
-/// - `Struct<T, U, V, ...>`
-///
-/// Nested types can themselves be nested within other arrays.
-/// For more information on these types please see
-/// [the physical memory layout of Apache
Arrow](https://arrow.apache.org/docs/format/Columnar.html#physical-memory-layout).
-#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub enum DataType {
- /// Null type
- Null,
- /// A boolean datatype representing the values `true` and `false`.
- Boolean,
- /// A signed 8-bit integer.
- Int8,
- /// A signed 16-bit integer.
- Int16,
- /// A signed 32-bit integer.
- Int32,
- /// A signed 64-bit integer.
- Int64,
- /// An unsigned 8-bit integer.
- UInt8,
- /// An unsigned 16-bit integer.
- UInt16,
- /// An unsigned 32-bit integer.
- UInt32,
- /// An unsigned 64-bit integer.
- UInt64,
- /// A 16-bit floating point number.
- Float16,
- /// A 32-bit floating point number.
- Float32,
- /// A 64-bit floating point number.
- Float64,
- /// A timestamp with an optional timezone.
- ///
- /// Time is measured as a Unix epoch, counting the seconds from
- /// 00:00:00.000 on 1 January 1970, excluding leap seconds,
- /// as a 64-bit integer.
- ///
- /// The time zone is a string indicating the name of a time zone, one of:
- ///
- /// * As used in the Olson time zone database (the "tz database" or
- /// "tzdata"), such as "America/New_York"
- /// * An absolute time zone offset of the form +XX:XX or -XX:XX, such as
+07:30
- ///
- /// Timestamps with a non-empty timezone
- /// ------------------------------------
- ///
- /// If a Timestamp column has a non-empty timezone value, its epoch is
- /// 1970-01-01 00:00:00 (January 1st 1970, midnight) in the *UTC* timezone
- /// (the Unix epoch), regardless of the Timestamp's own timezone.
- ///
- /// Therefore, timestamp values with a non-empty timezone correspond to
- /// physical points in time together with some additional information about
- /// how the data was obtained and/or how to display it (the timezone).
- ///
- /// For example, the timestamp value 0 with the timezone string
"Europe/Paris"
- /// corresponds to "January 1st 1970, 00h00" in the UTC timezone, but the
- /// application may prefer to display it as "January 1st 1970, 01h00" in
- /// the Europe/Paris timezone (which is the same physical point in time).
- ///
- /// One consequence is that timestamp values with a non-empty timezone
- /// can be compared and ordered directly, since they all share the same
- /// well-known point of reference (the Unix epoch).
- ///
- /// Timestamps with an unset / empty timezone
- /// -----------------------------------------
- ///
- /// If a Timestamp column has no timezone value, its epoch is
- /// 1970-01-01 00:00:00 (January 1st 1970, midnight) in an *unknown*
timezone.
- ///
- /// Therefore, timestamp values without a timezone cannot be meaningfully
- /// interpreted as physical points in time, but only as calendar / clock
- /// indications ("wall clock time") in an unspecified timezone.
- ///
- /// For example, the timestamp value 0 with an empty timezone string
- /// corresponds to "January 1st 1970, 00h00" in an unknown timezone:
there
- /// is not enough information to interpret it as a well-defined physical
- /// point in time.
- ///
- /// One consequence is that timestamp values without a timezone cannot
- /// be reliably compared or ordered, since they may have different points
of
- /// reference. In particular, it is *not* possible to interpret an unset
- /// or empty timezone as the same as "UTC".
- ///
- /// Conversion between timezones
- /// ----------------------------
- ///
- /// If a Timestamp column has a non-empty timezone, changing the timezone
- /// to a different non-empty value is a metadata-only operation:
- /// the timestamp values need not change as their point of reference
remains
- /// the same (the Unix epoch).
- ///
- /// However, if a Timestamp column has no timezone value, changing it to a
- /// non-empty value requires to think about the desired semantics.
- /// One possibility is to assume that the original timestamp values are
- /// relative to the epoch of the timezone being set; timestamp values
should
- /// then adjusted to the Unix epoch (for example, changing the timezone
from
- /// empty to "Europe/Paris" would require converting the timestamp values
- /// from "Europe/Paris" to "UTC", which seems counter-intuitive but is
- /// nevertheless correct).
- Timestamp(TimeUnit, Option<String>),
- /// A 32-bit date representing the elapsed time since UNIX epoch
(1970-01-01)
- /// in days (32 bits).
- Date32,
- /// A 64-bit date representing the elapsed time since UNIX epoch
(1970-01-01)
- /// in milliseconds (64 bits). Values are evenly divisible by 86400000.
- Date64,
- /// A 32-bit time representing the elapsed time since midnight in the unit
of `TimeUnit`.
- Time32(TimeUnit),
- /// A 64-bit time representing the elapsed time since midnight in the unit
of `TimeUnit`.
- Time64(TimeUnit),
- /// Measure of elapsed time in either seconds, milliseconds, microseconds
or nanoseconds.
- Duration(TimeUnit),
- /// A "calendar" interval which models types that don't necessarily
- /// have a precise duration without the context of a base timestamp (e.g.
- /// days can differ in length during day light savings time transitions).
- Interval(IntervalUnit),
- /// Opaque binary data of variable length.
- Binary,
- /// Opaque binary data of fixed size.
- /// Enum parameter specifies the number of bytes per value.
- FixedSizeBinary(i32),
- /// Opaque binary data of variable length and 64-bit offsets.
- LargeBinary,
- /// A variable-length string in Unicode with UTF-8 encoding.
- Utf8,
- /// A variable-length string in Unicode with UFT-8 encoding and 64-bit
offsets.
- LargeUtf8,
- /// A list of some logical data type with variable length.
- List(Box<Field>),
- /// A list of some logical data type with fixed length.
- FixedSizeList(Box<Field>, i32),
- /// A list of some logical data type with variable length and 64-bit
offsets.
- LargeList(Box<Field>),
- /// A nested datatype that contains a number of sub-fields.
- Struct(Vec<Field>),
- /// A nested datatype that can represent slots of differing types.
Components:
- ///
- /// 1. [`Field`] for each possible child type the Union can hold
- /// 2. The corresponding `type_id` used to identify which Field
- /// 3. The type of union (Sparse or Dense)
- Union(Vec<Field>, Vec<i8>, UnionMode),
- /// A dictionary encoded array (`key_type`, `value_type`), where
- /// each array element is an index of `key_type` into an
- /// associated dictionary of `value_type`.
- ///
- /// Dictionary arrays are used to store columns of `value_type`
- /// that contain many repeated values using less memory, but with
- /// a higher CPU overhead for some operations.
- ///
- /// This type mostly used to represent low cardinality string
- /// arrays or a limited set of primitive types as integers.
- Dictionary(Box<DataType>, Box<DataType>),
- /// Exact 128-bit width decimal value with precision and scale
- ///
- /// * precision is the total number of digits
- /// * scale is the number of digits past the decimal
- ///
- /// For example the number 123.45 has precision 5 and scale 2.
- Decimal128(u8, u8),
- /// Exact 256-bit width decimal value with precision and scale
- ///
- /// * precision is the total number of digits
- /// * scale is the number of digits past the decimal
- ///
- /// For example the number 123.45 has precision 5 and scale 2.
- Decimal256(u8, u8),
- /// A Map is a logical nested type that is represented as
- ///
- /// `List<entries: Struct<key: K, value: V>>`
- ///
- /// The keys and values are each respectively contiguous.
- /// The key and value types are not constrained, but keys should be
- /// hashable and unique.
- /// Whether the keys are sorted can be set in the `bool` after the `Field`.
- ///
- /// In a field with Map type, the field has a child Struct field, which
then
- /// has two children: key type and the second the value type. The names of
the
- /// child fields may be respectively "entries", "key", and "value", but
this is
- /// not enforced.
- Map(Box<Field>, bool),
-}
-
-/// An absolute length of time in seconds, milliseconds, microseconds or
nanoseconds.
-#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub enum TimeUnit {
- /// Time in seconds.
- Second,
- /// Time in milliseconds.
- Millisecond,
- /// Time in microseconds.
- Microsecond,
- /// Time in nanoseconds.
- Nanosecond,
-}
-
-/// YEAR_MONTH, DAY_TIME, MONTH_DAY_NANO interval in SQL style.
-#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub enum IntervalUnit {
- /// Indicates the number of elapsed whole months, stored as 4-byte
integers.
- YearMonth,
- /// Indicates the number of elapsed days and milliseconds,
- /// stored as 2 contiguous 32-bit integers (days, milliseconds) (8-bytes
in total).
- DayTime,
- /// A triple of the number of elapsed months, days, and nanoseconds.
- /// The values are stored contiguously in 16 byte blocks. Months and
- /// days are encoded as 32 bit integers and nanoseconds is encoded as a
- /// 64 bit integer. All integers are signed. Each field is independent
- /// (e.g. there is no constraint that nanoseconds have the same sign
- /// as days or that the quantity of nanoseconds represents less
- /// than a day's worth of time).
- MonthDayNano,
-}
-
-// Sparse or Dense union layouts
-#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub enum UnionMode {
- Sparse,
- Dense,
-}
-
-impl fmt::Display for DataType {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{:?}", self)
- }
-}
+use num::BigInt;
+use std::cmp::Ordering;
// MAX decimal256 value of little-endian format for each precision.
// Each element is the max value of signed 256-bit integer for the specified
precision which
@@ -887,7 +641,7 @@ pub(crate) const
MIN_DECIMAL_BYTES_FOR_LARGER_EACH_PRECISION: [[u8; 32]; 76] = [
];
/// `MAX_DECIMAL_FOR_EACH_PRECISION[p]` holds the maximum `i128` value
-/// that can be stored in [DataType::Decimal128] value of precision `p`
+/// that can be stored in [arrow_schema::DataType::Decimal128] value of
precision `p`
pub const MAX_DECIMAL_FOR_EACH_PRECISION: [i128; 38] = [
9,
99,
@@ -930,7 +684,7 @@ pub const MAX_DECIMAL_FOR_EACH_PRECISION: [i128; 38] = [
];
/// `MIN_DECIMAL_FOR_EACH_PRECISION[p]` holds the minimum `i128` value
-/// that can be stored in a [DataType::Decimal128] value of precision `p`
+/// that can be stored in a [arrow_schema::DataType::Decimal128] value of
precision `p`
pub const MIN_DECIMAL_FOR_EACH_PRECISION: [i128; 38] = [
-9,
-99,
@@ -972,19 +726,20 @@ pub const MIN_DECIMAL_FOR_EACH_PRECISION: [i128; 38] = [
-99999999999999999999999999999999999999,
];
-/// The maximum precision for [DataType::Decimal128] values
+/// The maximum precision for [arrow_schema::DataType::Decimal128] values
pub const DECIMAL128_MAX_PRECISION: u8 = 38;
-/// The maximum scale for [DataType::Decimal128] values
+/// The maximum scale for [arrow_schema::DataType::Decimal128] values
pub const DECIMAL128_MAX_SCALE: u8 = 38;
-/// The maximum precision for [DataType::Decimal256] values
+/// The maximum precision for [arrow_schema::DataType::Decimal256] values
pub const DECIMAL256_MAX_PRECISION: u8 = 76;
-/// The maximum scale for [DataType::Decimal256] values
+/// The maximum scale for [arrow_schema::DataType::Decimal256] values
pub const DECIMAL256_MAX_SCALE: u8 = 76;
-/// The default scale for [DataType::Decimal128] and [DataType::Decimal256]
values
+/// The default scale for [arrow_schema::DataType::Decimal128] and
+/// [arrow_schema::DataType::Decimal256] values
pub const DECIMAL_DEFAULT_SCALE: u8 = 10;
/// Validates that the specified `i128` value can be properly
@@ -1051,124 +806,9 @@ pub(crate) fn
validate_decimal256_precision_with_lt_bytes(
}
}
-impl DataType {
- /// Returns true if this type is numeric: (UInt*, Int*, or Float*).
- pub fn is_numeric(t: &DataType) -> bool {
- use DataType::*;
- matches!(
- t,
- UInt8
- | UInt16
- | UInt32
- | UInt64
- | Int8
- | Int16
- | Int32
- | Int64
- | Float32
- | Float64
- )
- }
-
- /// Returns true if the type is primitive: (numeric, temporal).
- pub fn is_primitive(t: &DataType) -> bool {
- use DataType::*;
- matches!(
- t,
- Int8 | Int16
- | Int32
- | Int64
- | UInt8
- | UInt16
- | UInt32
- | UInt64
- | Float32
- | Float64
- | Date32
- | Date64
- | Time32(_)
- | Time64(_)
- | Timestamp(_, _)
- | Interval(_)
- | Duration(_)
- )
- }
-
- /// Returns true if this type is temporal: (Date*, Time*, Duration, or
Interval).
- pub fn is_temporal(t: &DataType) -> bool {
- use DataType::*;
- matches!(
- t,
- Date32
- | Date64
- | Timestamp(_, _)
- | Time32(_)
- | Time64(_)
- | Duration(_)
- | Interval(_)
- )
- }
-
- /// Returns true if this type is valid as a dictionary key
- /// (e.g. [`super::ArrowDictionaryKeyType`]
- pub fn is_dictionary_key_type(t: &DataType) -> bool {
- use DataType::*;
- matches!(
- t,
- UInt8 | UInt16 | UInt32 | UInt64 | Int8 | Int16 | Int32 | Int64
- )
- }
-
- /// Returns true if this type is nested (List, FixedSizeList, LargeList,
Struct, Union, or Map)
- pub fn is_nested(t: &DataType) -> bool {
- use DataType::*;
- matches!(
- t,
- List(_)
- | FixedSizeList(_, _)
- | LargeList(_)
- | Struct(_)
- | Union(_, _, _)
- | Map(_, _)
- )
- }
-
- /// Compares the datatype with another, ignoring nested field names
- /// and metadata.
- pub fn equals_datatype(&self, other: &DataType) -> bool {
- match (&self, other) {
- (DataType::List(a), DataType::List(b))
- | (DataType::LargeList(a), DataType::LargeList(b)) => {
- a.is_nullable() == b.is_nullable()
- && a.data_type().equals_datatype(b.data_type())
- }
- (DataType::FixedSizeList(a, a_size), DataType::FixedSizeList(b,
b_size)) => {
- a_size == b_size
- && a.is_nullable() == b.is_nullable()
- && a.data_type().equals_datatype(b.data_type())
- }
- (DataType::Struct(a), DataType::Struct(b)) => {
- a.len() == b.len()
- && a.iter().zip(b).all(|(a, b)| {
- a.is_nullable() == b.is_nullable()
- && a.data_type().equals_datatype(b.data_type())
- })
- }
- (
- DataType::Map(a_field, a_is_sorted),
- DataType::Map(b_field, b_is_sorted),
- ) => a_field == b_field && a_is_sorted == b_is_sorted,
- _ => self == other,
- }
- }
-}
-
#[cfg(test)]
mod test {
- use crate::datatypes::datatype::{
- MAX_DECIMAL_BYTES_FOR_LARGER_EACH_PRECISION,
- MIN_DECIMAL_BYTES_FOR_LARGER_EACH_PRECISION,
- };
+ use super::*;
use crate::util::decimal::Decimal256;
use num::{BigInt, Num};
diff --git a/arrow/src/datatypes/mod.rs b/arrow/src/datatypes/mod.rs
index 1586d563c..2f8387112 100644
--- a/arrow/src/datatypes/mod.rs
+++ b/arrow/src/datatypes/mod.rs
@@ -26,17 +26,15 @@ use std::sync::Arc;
mod native;
pub use native::*;
-mod field;
-pub use field::*;
-mod schema;
-pub use schema::*;
mod numeric;
pub use numeric::*;
mod types;
pub use types::*;
-mod datatype;
-pub use datatype::*;
+mod decimal;
mod delta;
+pub use decimal::*;
+
+pub use arrow_schema::{DataType, Field, IntervalUnit, Schema, TimeUnit,
UnionMode};
#[cfg(feature = "ffi")]
mod ffi;
@@ -45,550 +43,3 @@ pub use ffi::*;
/// A reference-counted reference to a [`Schema`](crate::datatypes::Schema).
pub type SchemaRef = Arc<Schema>;
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::error::Result;
- use std::collections::{BTreeMap, HashMap};
-
- #[cfg(feature = "json")]
- use crate::json::JsonSerializable;
-
- #[cfg(feature = "json")]
- use serde_json::{
- Number,
- Value::{Bool, Number as VNumber, String as VString},
- };
-
- #[test]
- fn test_list_datatype_equality() {
- // tests that list type equality is checked while ignoring list names
- let list_a = DataType::List(Box::new(Field::new("item",
DataType::Int32, true)));
- let list_b = DataType::List(Box::new(Field::new("array",
DataType::Int32, true)));
- let list_c = DataType::List(Box::new(Field::new("item",
DataType::Int32, false)));
- let list_d = DataType::List(Box::new(Field::new("item",
DataType::UInt32, true)));
- assert!(list_a.equals_datatype(&list_b));
- assert!(!list_a.equals_datatype(&list_c));
- assert!(!list_b.equals_datatype(&list_c));
- assert!(!list_a.equals_datatype(&list_d));
-
- let list_e =
- DataType::FixedSizeList(Box::new(Field::new("item", list_a,
false)), 3);
- let list_f =
- DataType::FixedSizeList(Box::new(Field::new("array", list_b,
false)), 3);
- let list_g = DataType::FixedSizeList(
- Box::new(Field::new("item", DataType::FixedSizeBinary(3), true)),
- 3,
- );
- assert!(list_e.equals_datatype(&list_f));
- assert!(!list_e.equals_datatype(&list_g));
- assert!(!list_f.equals_datatype(&list_g));
-
- let list_h = DataType::Struct(vec![Field::new("f1", list_e, true)]);
- let list_i = DataType::Struct(vec![Field::new("f1", list_f.clone(),
true)]);
- let list_j = DataType::Struct(vec![Field::new("f1", list_f.clone(),
false)]);
- let list_k = DataType::Struct(vec![
- Field::new("f1", list_f.clone(), false),
- Field::new("f2", list_g.clone(), false),
- Field::new("f3", DataType::Utf8, true),
- ]);
- let list_l = DataType::Struct(vec![
- Field::new("ff1", list_f.clone(), false),
- Field::new("ff2", list_g.clone(), false),
- Field::new("ff3", DataType::LargeUtf8, true),
- ]);
- let list_m = DataType::Struct(vec![
- Field::new("ff1", list_f, false),
- Field::new("ff2", list_g, false),
- Field::new("ff3", DataType::Utf8, true),
- ]);
- assert!(list_h.equals_datatype(&list_i));
- assert!(!list_h.equals_datatype(&list_j));
- assert!(!list_k.equals_datatype(&list_l));
- assert!(list_k.equals_datatype(&list_m));
- }
-
- #[test]
- #[cfg(feature = "json")]
- fn create_struct_type() {
- let _person = DataType::Struct(vec![
- Field::new("first_name", DataType::Utf8, false),
- Field::new("last_name", DataType::Utf8, false),
- Field::new(
- "address",
- DataType::Struct(vec![
- Field::new("street", DataType::Utf8, false),
- Field::new("zip", DataType::UInt16, false),
- ]),
- false,
- ),
- ]);
- }
-
- #[test]
- #[cfg(feature = "json")]
- fn serde_struct_type() {
- let kv_array = [("k".to_string(), "v".to_string())];
- let field_metadata: BTreeMap<String, String> =
kv_array.iter().cloned().collect();
-
- // Non-empty map: should be converted as JSON obj { ... }
- let first_name = Field::new("first_name", DataType::Utf8, false)
- .with_metadata(Some(field_metadata));
-
- // Empty map: should be omitted.
- let last_name = Field::new("last_name", DataType::Utf8, false)
- .with_metadata(Some(BTreeMap::default()));
-
- let person = DataType::Struct(vec![
- first_name,
- last_name,
- Field::new(
- "address",
- DataType::Struct(vec![
- Field::new("street", DataType::Utf8, false),
- Field::new("zip", DataType::UInt16, false),
- ]),
- false,
- ),
- ]);
-
- let serialized = serde_json::to_string(&person).unwrap();
-
- // NOTE that this is testing the default (derived) serialization
format, not the
- // JSON format specified in metadata.md
-
- assert_eq!(
- "{\"Struct\":[\
-
{\"name\":\"first_name\",\"data_type\":\"Utf8\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false,\"metadata\":{\"k\":\"v\"}},\
-
{\"name\":\"last_name\",\"data_type\":\"Utf8\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false},\
- {\"name\":\"address\",\"data_type\":{\"Struct\":\
-
[{\"name\":\"street\",\"data_type\":\"Utf8\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false},\
-
{\"name\":\"zip\",\"data_type\":\"UInt16\",\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false}\
- ]},\"nullable\":false,\"dict_id\":0,\"dict_is_ordered\":false}]}",
- serialized
- );
-
- let deserialized = serde_json::from_str(&serialized).unwrap();
-
- assert_eq!(person, deserialized);
- }
-
- #[test]
- fn create_schema_string() {
- let schema = person_schema();
- assert_eq!(schema.to_string(),
- "Field { name: \"first_name\", data_type: Utf8, nullable: false,
dict_id: 0, dict_is_ordered: false, metadata: Some({\"k\": \"v\"}) }, \
- Field { name: \"last_name\", data_type: Utf8, nullable: false,
dict_id: 0, dict_is_ordered: false, metadata: None }, \
- Field { name: \"address\", data_type: Struct([\
- Field { name: \"street\", data_type: Utf8, nullable: false,
dict_id: 0, dict_is_ordered: false, metadata: None }, \
- Field { name: \"zip\", data_type: UInt16, nullable: false,
dict_id: 0, dict_is_ordered: false, metadata: None }\
- ]), nullable: false, dict_id: 0, dict_is_ordered: false, metadata:
None }, \
- Field { name: \"interests\", data_type: Dictionary(Int32, Utf8),
nullable: true, dict_id: 123, dict_is_ordered: true, metadata: None }")
- }
-
- #[test]
- fn schema_field_accessors() {
- let schema = person_schema();
-
- // test schema accessors
- assert_eq!(schema.fields().len(), 4);
-
- // test field accessors
- let first_name = &schema.fields()[0];
- assert_eq!(first_name.name(), "first_name");
- assert_eq!(first_name.data_type(), &DataType::Utf8);
- assert!(!first_name.is_nullable());
- assert_eq!(first_name.dict_id(), None);
- assert_eq!(first_name.dict_is_ordered(), None);
-
- let metadata = first_name.metadata();
- assert!(metadata.is_some());
- let md = metadata.as_ref().unwrap();
- assert_eq!(md.len(), 1);
- let key = md.get("k");
- assert!(key.is_some());
- assert_eq!(key.unwrap(), "v");
-
- let interests = &schema.fields()[3];
- assert_eq!(interests.name(), "interests");
- assert_eq!(
- interests.data_type(),
- &DataType::Dictionary(Box::new(DataType::Int32),
Box::new(DataType::Utf8))
- );
- assert_eq!(interests.dict_id(), Some(123));
- assert_eq!(interests.dict_is_ordered(), Some(true));
- }
-
- #[test]
- #[should_panic(
- expected = "Unable to get field named \\\"nickname\\\". Valid fields:
[\\\"first_name\\\", \\\"last_name\\\", \\\"address\\\", \\\"interests\\\"]"
- )]
- fn schema_index_of() {
- let schema = person_schema();
- assert_eq!(schema.index_of("first_name").unwrap(), 0);
- assert_eq!(schema.index_of("last_name").unwrap(), 1);
- schema.index_of("nickname").unwrap();
- }
-
- #[test]
- #[should_panic(
- expected = "Unable to get field named \\\"nickname\\\". Valid fields:
[\\\"first_name\\\", \\\"last_name\\\", \\\"address\\\", \\\"interests\\\"]"
- )]
- fn schema_field_with_name() {
- let schema = person_schema();
- assert_eq!(
- schema.field_with_name("first_name").unwrap().name(),
- "first_name"
- );
- assert_eq!(
- schema.field_with_name("last_name").unwrap().name(),
- "last_name"
- );
- schema.field_with_name("nickname").unwrap();
- }
-
- #[test]
- fn schema_field_with_dict_id() {
- let schema = person_schema();
-
- let fields_dict_123: Vec<_> = schema
- .fields_with_dict_id(123)
- .iter()
- .map(|f| f.name())
- .collect();
- assert_eq!(fields_dict_123, vec!["interests"]);
-
- assert!(schema.fields_with_dict_id(456).is_empty());
- }
-
- #[test]
- fn schema_equality() {
- let schema1 = Schema::new(vec![
- Field::new("c1", DataType::Utf8, false),
- Field::new("c2", DataType::Float64, true),
- Field::new("c3", DataType::LargeBinary, true),
- ]);
- let schema2 = Schema::new(vec![
- Field::new("c1", DataType::Utf8, false),
- Field::new("c2", DataType::Float64, true),
- Field::new("c3", DataType::LargeBinary, true),
- ]);
-
- assert_eq!(schema1, schema2);
-
- let schema3 = Schema::new(vec![
- Field::new("c1", DataType::Utf8, false),
- Field::new("c2", DataType::Float32, true),
- ]);
- let schema4 = Schema::new(vec![
- Field::new("C1", DataType::Utf8, false),
- Field::new("C2", DataType::Float64, true),
- ]);
-
- assert!(schema1 != schema3);
- assert!(schema1 != schema4);
- assert!(schema2 != schema3);
- assert!(schema2 != schema4);
- assert!(schema3 != schema4);
-
- let f = Field::new("c1", DataType::Utf8, false).with_metadata(Some(
- [("foo".to_string(), "bar".to_string())]
- .iter()
- .cloned()
- .collect(),
- ));
- let schema5 = Schema::new(vec![
- f,
- Field::new("c2", DataType::Float64, true),
- Field::new("c3", DataType::LargeBinary, true),
- ]);
- assert!(schema1 != schema5);
- }
-
- #[test]
- #[cfg(feature = "json")]
- fn test_arrow_native_type_to_json() {
- assert_eq!(Some(Bool(true)), true.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1i8.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1i16.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1i32.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1i64.into_json_value());
- assert_eq!(Some(VString("1".to_string())), 1i128.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1u8.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1u16.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1u32.into_json_value());
- assert_eq!(Some(VNumber(Number::from(1))), 1u64.into_json_value());
- assert_eq!(
- Some(VNumber(Number::from_f64(0.01f64).unwrap())),
- 0.01.into_json_value()
- );
- assert_eq!(
- Some(VNumber(Number::from_f64(0.01f64).unwrap())),
- 0.01f64.into_json_value()
- );
- assert_eq!(None, f32::NAN.into_json_value());
- }
-
- fn person_schema() -> Schema {
- let kv_array = [("k".to_string(), "v".to_string())];
- let field_metadata: BTreeMap<String, String> =
kv_array.iter().cloned().collect();
- let first_name = Field::new("first_name", DataType::Utf8, false)
- .with_metadata(Some(field_metadata));
-
- Schema::new(vec![
- first_name,
- Field::new("last_name", DataType::Utf8, false),
- Field::new(
- "address",
- DataType::Struct(vec![
- Field::new("street", DataType::Utf8, false),
- Field::new("zip", DataType::UInt16, false),
- ]),
- false,
- ),
- Field::new_dict(
- "interests",
- DataType::Dictionary(Box::new(DataType::Int32),
Box::new(DataType::Utf8)),
- true,
- 123,
- true,
- ),
- ])
- }
-
- #[test]
- fn test_try_merge_field_with_metadata() {
- // 1. Different values for the same key should cause error.
- let metadata1: BTreeMap<String, String> =
- [("foo".to_string(), "bar".to_string())]
- .iter()
- .cloned()
- .collect();
- let f1 = Field::new("first_name", DataType::Utf8, false)
- .with_metadata(Some(metadata1));
-
- let metadata2: BTreeMap<String, String> =
- [("foo".to_string(), "baz".to_string())]
- .iter()
- .cloned()
- .collect();
- let f2 = Field::new("first_name", DataType::Utf8, false)
- .with_metadata(Some(metadata2));
-
- assert!(
- Schema::try_merge(vec![Schema::new(vec![f1]),
Schema::new(vec![f2])])
- .is_err()
- );
-
- // 2. None + Some
- let mut f1 = Field::new("first_name", DataType::Utf8, false);
- let metadata2: BTreeMap<String, String> =
- [("missing".to_string(), "value".to_string())]
- .iter()
- .cloned()
- .collect();
- let f2 = Field::new("first_name", DataType::Utf8, false)
- .with_metadata(Some(metadata2));
-
- assert!(f1.try_merge(&f2).is_ok());
- assert!(f1.metadata().is_some());
- assert_eq!(
- f1.metadata().as_ref().unwrap(),
- f2.metadata().as_ref().unwrap()
- );
-
- // 3. Some + Some
- let mut f1 = Field::new("first_name", DataType::Utf8,
false).with_metadata(Some(
- [("foo".to_string(), "bar".to_string())]
- .iter()
- .cloned()
- .collect(),
- ));
- let f2 = Field::new("first_name", DataType::Utf8,
false).with_metadata(Some(
- [("foo2".to_string(), "bar2".to_string())]
- .iter()
- .cloned()
- .collect(),
- ));
-
- assert!(f1.try_merge(&f2).is_ok());
- assert!(f1.metadata().is_some());
- assert_eq!(
- f1.metadata().cloned().unwrap(),
- [
- ("foo".to_string(), "bar".to_string()),
- ("foo2".to_string(), "bar2".to_string())
- ]
- .iter()
- .cloned()
- .collect()
- );
-
- // 4. Some + None.
- let mut f1 = Field::new("first_name", DataType::Utf8,
false).with_metadata(Some(
- [("foo".to_string(), "bar".to_string())]
- .iter()
- .cloned()
- .collect(),
- ));
- let f2 = Field::new("first_name", DataType::Utf8, false);
- assert!(f1.try_merge(&f2).is_ok());
- assert!(f1.metadata().is_some());
- assert_eq!(
- f1.metadata().cloned().unwrap(),
- [("foo".to_string(), "bar".to_string())]
- .iter()
- .cloned()
- .collect()
- );
-
- // 5. None + None.
- let mut f1 = Field::new("first_name", DataType::Utf8, false);
- let f2 = Field::new("first_name", DataType::Utf8, false);
- assert!(f1.try_merge(&f2).is_ok());
- assert!(f1.metadata().is_none());
- }
-
- #[test]
- fn test_schema_merge() -> Result<()> {
- let merged = Schema::try_merge(vec![
- Schema::new(vec![
- Field::new("first_name", DataType::Utf8, false),
- Field::new("last_name", DataType::Utf8, false),
- Field::new(
- "address",
- DataType::Struct(vec![Field::new("zip", DataType::UInt16,
false)]),
- false,
- ),
- ]),
- Schema::new_with_metadata(
- vec![
- // nullable merge
- Field::new("last_name", DataType::Utf8, true),
- Field::new(
- "address",
- DataType::Struct(vec![
- // add new nested field
- Field::new("street", DataType::Utf8, false),
- // nullable merge on nested field
- Field::new("zip", DataType::UInt16, true),
- ]),
- false,
- ),
- // new field
- Field::new("number", DataType::Utf8, true),
- ],
- [("foo".to_string(), "bar".to_string())]
- .iter()
- .cloned()
- .collect::<HashMap<String, String>>(),
- ),
- ])?;
-
- assert_eq!(
- merged,
- Schema::new_with_metadata(
- vec![
- Field::new("first_name", DataType::Utf8, false),
- Field::new("last_name", DataType::Utf8, true),
- Field::new(
- "address",
- DataType::Struct(vec![
- Field::new("zip", DataType::UInt16, true),
- Field::new("street", DataType::Utf8, false),
- ]),
- false,
- ),
- Field::new("number", DataType::Utf8, true),
- ],
- [("foo".to_string(), "bar".to_string())]
- .iter()
- .cloned()
- .collect::<HashMap<String, String>>()
- )
- );
-
- // support merge union fields
- assert_eq!(
- Schema::try_merge(vec![
- Schema::new(vec![Field::new(
- "c1",
- DataType::Union(
- vec![
- Field::new("c11", DataType::Utf8, true),
- Field::new("c12", DataType::Utf8, true),
- ],
- vec![0, 1],
- UnionMode::Dense
- ),
- false
- ),]),
- Schema::new(vec![Field::new(
- "c1",
- DataType::Union(
- vec![
- Field::new("c12", DataType::Utf8, true),
- Field::new("c13",
DataType::Time64(TimeUnit::Second), true),
- ],
- vec![1, 2],
- UnionMode::Dense
- ),
- false
- ),])
- ])?,
- Schema::new(vec![Field::new(
- "c1",
- DataType::Union(
- vec![
- Field::new("c11", DataType::Utf8, true),
- Field::new("c12", DataType::Utf8, true),
- Field::new("c13", DataType::Time64(TimeUnit::Second),
true),
- ],
- vec![0, 1, 2],
- UnionMode::Dense
- ),
- false
- ),]),
- );
-
- // incompatible field should throw error
- assert!(Schema::try_merge(vec![
- Schema::new(vec![
- Field::new("first_name", DataType::Utf8, false),
- Field::new("last_name", DataType::Utf8, false),
- ]),
- Schema::new(vec![Field::new("last_name", DataType::Int64, false),])
- ])
- .is_err());
-
- // incompatible metadata should throw error
- let res = Schema::try_merge(vec![
- Schema::new_with_metadata(
- vec![Field::new("first_name", DataType::Utf8, false)],
- [("foo".to_string(), "bar".to_string())]
- .iter()
- .cloned()
- .collect::<HashMap<String, String>>(),
- ),
- Schema::new_with_metadata(
- vec![Field::new("last_name", DataType::Utf8, false)],
- [("foo".to_string(), "baz".to_string())]
- .iter()
- .cloned()
- .collect::<HashMap<String, String>>(),
- ),
- ])
- .unwrap_err();
-
- let expected = "Fail to merge schema due to conflicting metadata. Key
'foo' has different values 'bar' and 'baz'";
- assert!(
- res.to_string().contains(expected),
- "Could not find expected string '{}' in '{}'",
- expected,
- res
- );
-
- Ok(())
- }
-}
diff --git a/arrow/src/datatypes/schema.rs b/arrow/src/datatypes/schema.rs
deleted file mode 100644
index b0eca6114..000000000
--- a/arrow/src/datatypes/schema.rs
+++ /dev/null
@@ -1,386 +0,0 @@
-// 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.
-
-use std::collections::HashMap;
-use std::fmt;
-use std::hash::Hash;
-
-use crate::error::{ArrowError, Result};
-
-use super::Field;
-
-/// Describes the meta-data of an ordered sequence of relative types.
-///
-/// Note that this information is only part of the meta-data and not part of
the physical
-/// memory layout.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub struct Schema {
- pub fields: Vec<Field>,
- /// A map of key-value pairs containing additional meta data.
- #[cfg_attr(
- feature = "serde",
- serde(skip_serializing_if = "HashMap::is_empty", default)
- )]
- pub metadata: HashMap<String, String>,
-}
-
-impl Schema {
- /// Creates an empty `Schema`
- pub fn empty() -> Self {
- Self {
- fields: vec![],
- metadata: HashMap::new(),
- }
- }
-
- /// Creates a new [`Schema`] from a sequence of [`Field`] values.
- ///
- /// # Example
- ///
- /// ```
- /// # use arrow::datatypes::{Field, DataType, Schema};
- /// let field_a = Field::new("a", DataType::Int64, false);
- /// let field_b = Field::new("b", DataType::Boolean, false);
- ///
- /// let schema = Schema::new(vec![field_a, field_b]);
- /// ```
- pub fn new(fields: Vec<Field>) -> Self {
- Self::new_with_metadata(fields, HashMap::new())
- }
-
- /// Creates a new [`Schema`] from a sequence of [`Field`] values
- /// and adds additional metadata in form of key value pairs.
- ///
- /// # Example
- ///
- /// ```
- /// # use arrow::datatypes::{Field, DataType, Schema};
- /// # use std::collections::HashMap;
- /// let field_a = Field::new("a", DataType::Int64, false);
- /// let field_b = Field::new("b", DataType::Boolean, false);
- ///
- /// let mut metadata: HashMap<String, String> = HashMap::new();
- /// metadata.insert("row_count".to_string(), "100".to_string());
- ///
- /// let schema = Schema::new_with_metadata(vec![field_a, field_b],
metadata);
- /// ```
- #[inline]
- pub const fn new_with_metadata(
- fields: Vec<Field>,
- metadata: HashMap<String, String>,
- ) -> Self {
- Self { fields, metadata }
- }
-
- /// Sets the metadata of this `Schema` to be `metadata` and returns self
- pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
- self.metadata = metadata;
- self
- }
-
- /// Returns a new schema with only the specified columns in the new schema
- /// This carries metadata from the parent schema over as well
- pub fn project(&self, indices: &[usize]) -> Result<Schema> {
- let new_fields = indices
- .iter()
- .map(|i| {
- self.fields.get(*i).cloned().ok_or_else(|| {
- ArrowError::SchemaError(format!(
- "project index {} out of bounds, max field {}",
- i,
- self.fields().len()
- ))
- })
- })
- .collect::<Result<Vec<_>>>()?;
- Ok(Self::new_with_metadata(new_fields, self.metadata.clone()))
- }
-
- /// Merge schema into self if it is compatible. Struct fields will be
merged recursively.
- ///
- /// Example:
- ///
- /// ```
- /// use arrow::datatypes::*;
- ///
- /// let merged = Schema::try_merge(vec![
- /// Schema::new(vec![
- /// Field::new("c1", DataType::Int64, false),
- /// Field::new("c2", DataType::Utf8, false),
- /// ]),
- /// Schema::new(vec![
- /// Field::new("c1", DataType::Int64, true),
- /// Field::new("c2", DataType::Utf8, false),
- /// Field::new("c3", DataType::Utf8, false),
- /// ]),
- /// ]).unwrap();
- ///
- /// assert_eq!(
- /// merged,
- /// Schema::new(vec![
- /// Field::new("c1", DataType::Int64, true),
- /// Field::new("c2", DataType::Utf8, false),
- /// Field::new("c3", DataType::Utf8, false),
- /// ]),
- /// );
- /// ```
- pub fn try_merge(schemas: impl IntoIterator<Item = Self>) -> Result<Self> {
- schemas
- .into_iter()
- .try_fold(Self::empty(), |mut merged, schema| {
- let Schema { metadata, fields } = schema;
- for (key, value) in metadata.into_iter() {
- // merge metadata
- if let Some(old_val) = merged.metadata.get(&key) {
- if old_val != &value {
- return Err(ArrowError::SchemaError(format!(
- "Fail to merge schema due to conflicting
metadata. \
- Key '{}' has different values '{}'
and '{}'",
- key, old_val, value
- )));
- }
- }
- merged.metadata.insert(key, value);
- }
- // merge fields
- for field in fields.into_iter() {
- let merged_field =
- merged.fields.iter_mut().find(|f| f.name() ==
field.name());
- match merged_field {
- Some(merged_field) => merged_field.try_merge(&field)?,
- // found a new field, add to field list
- None => merged.fields.push(field),
- }
- }
- Ok(merged)
- })
- }
-
- /// Returns an immutable reference of the vector of `Field` instances.
- #[inline]
- pub const fn fields(&self) -> &Vec<Field> {
- &self.fields
- }
-
- /// Returns a vector with references to all fields (including nested
fields)
- #[inline]
- #[cfg(feature = "ipc")]
- pub(crate) fn all_fields(&self) -> Vec<&Field> {
- self.fields.iter().flat_map(|f| f.fields()).collect()
- }
-
- /// Returns an immutable reference of a specific [`Field`] instance
selected using an
- /// offset within the internal `fields` vector.
- pub fn field(&self, i: usize) -> &Field {
- &self.fields[i]
- }
-
- /// Returns an immutable reference of a specific [`Field`] instance
selected by name.
- pub fn field_with_name(&self, name: &str) -> Result<&Field> {
- Ok(&self.fields[self.index_of(name)?])
- }
-
- /// Returns a vector of immutable references to all [`Field`] instances
selected by
- /// the dictionary ID they use.
- pub fn fields_with_dict_id(&self, dict_id: i64) -> Vec<&Field> {
- self.fields
- .iter()
- .flat_map(|f| f.fields_with_dict_id(dict_id))
- .collect()
- }
-
- /// Find the index of the column with the given name.
- pub fn index_of(&self, name: &str) -> Result<usize> {
- (0..self.fields.len())
- .find(|idx| self.fields[*idx].name() == name)
- .ok_or_else(|| {
- let valid_fields: Vec<String> =
- self.fields.iter().map(|f| f.name().clone()).collect();
- ArrowError::InvalidArgumentError(format!(
- "Unable to get field named \"{}\". Valid fields: {:?}",
- name, valid_fields
- ))
- })
- }
-
- /// Returns an immutable reference to the Map of custom metadata key-value
pairs.
- #[inline]
- pub const fn metadata(&self) -> &HashMap<String, String> {
- &self.metadata
- }
-
- /// Look up a column by name and return a immutable reference to the
column along with
- /// its index.
- pub fn column_with_name(&self, name: &str) -> Option<(usize, &Field)> {
- self.fields
- .iter()
- .enumerate()
- .find(|&(_, c)| c.name() == name)
- }
-
- /// Check to see if `self` is a superset of `other` schema. Here are the
comparison rules:
- ///
- /// * `self` and `other` should contain the same number of fields
- /// * for every field `f` in `other`, the field in `self` with
corresponding index should be a
- /// superset of `f`.
- /// * self.metadata is a superset of other.metadata
- ///
- /// In other words, any record conforms to `other` should also conform to
`self`.
- pub fn contains(&self, other: &Schema) -> bool {
- self.fields.len() == other.fields.len()
- && self.fields.iter().zip(other.fields.iter()).all(|(f1, f2)|
f1.contains(f2))
- // make sure self.metadata is a superset of other.metadata
- && other.metadata.iter().all(|(k, v1)| match self.metadata.get(k) {
- Some(v2) => v1 == v2,
- _ => false,
- })
- }
-}
-
-impl fmt::Display for Schema {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str(
- &self
- .fields
- .iter()
- .map(|c| c.to_string())
- .collect::<Vec<String>>()
- .join(", "),
- )
- }
-}
-
-// need to implement `Hash` manually because `HashMap` implement Eq but no
`Hash`
-#[allow(clippy::derive_hash_xor_eq)]
-impl Hash for Schema {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- self.fields.hash(state);
-
- // ensure deterministic key order
- let mut keys: Vec<&String> = self.metadata.keys().collect();
- keys.sort();
- for k in keys {
- k.hash(state);
- self.metadata.get(k).expect("key valid").hash(state);
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::datatypes::DataType;
-
- use super::*;
-
- #[test]
- #[cfg(feature = "json")]
- fn test_ser_de_metadata() {
- // ser/de with empty metadata
- let schema = Schema::new(vec![
- Field::new("name", DataType::Utf8, false),
- Field::new("address", DataType::Utf8, false),
- Field::new("priority", DataType::UInt8, false),
- ]);
-
- let json = serde_json::to_string(&schema).unwrap();
- let de_schema = serde_json::from_str(&json).unwrap();
-
- assert_eq!(schema, de_schema);
-
- // ser/de with non-empty metadata
- let schema = schema
- .with_metadata([("key".to_owned(),
"val".to_owned())].into_iter().collect());
- let json = serde_json::to_string(&schema).unwrap();
- let de_schema = serde_json::from_str(&json).unwrap();
-
- assert_eq!(schema, de_schema);
- }
-
- #[test]
- fn test_projection() {
- let mut metadata = HashMap::new();
- metadata.insert("meta".to_string(), "data".to_string());
-
- let schema = Schema::new(vec![
- Field::new("name", DataType::Utf8, false),
- Field::new("address", DataType::Utf8, false),
- Field::new("priority", DataType::UInt8, false),
- ])
- .with_metadata(metadata);
-
- let projected: Schema = schema.project(&[0, 2]).unwrap();
-
- assert_eq!(projected.fields().len(), 2);
- assert_eq!(projected.fields()[0].name(), "name");
- assert_eq!(projected.fields()[1].name(), "priority");
- assert_eq!(projected.metadata.get("meta").unwrap(), "data")
- }
-
- #[test]
- fn test_oob_projection() {
- let mut metadata = HashMap::new();
- metadata.insert("meta".to_string(), "data".to_string());
-
- let schema = Schema::new(vec![
- Field::new("name", DataType::Utf8, false),
- Field::new("address", DataType::Utf8, false),
- Field::new("priority", DataType::UInt8, false),
- ])
- .with_metadata(metadata);
-
- let projected: Result<Schema> = schema.project(&[0, 3]);
-
- assert!(projected.is_err());
- if let Err(e) = projected {
- assert_eq!(
- e.to_string(),
- "Schema error: project index 3 out of bounds, max field
3".to_string()
- )
- }
- }
-
- #[test]
- fn test_schema_contains() {
- let mut metadata1 = HashMap::new();
- metadata1.insert("meta".to_string(), "data".to_string());
-
- let schema1 = Schema::new(vec![
- Field::new("name", DataType::Utf8, false),
- Field::new("address", DataType::Utf8, false),
- Field::new("priority", DataType::UInt8, false),
- ])
- .with_metadata(metadata1.clone());
-
- let mut metadata2 = HashMap::new();
- metadata2.insert("meta".to_string(), "data".to_string());
- metadata2.insert("meta2".to_string(), "data".to_string());
- let schema2 = Schema::new(vec![
- Field::new("name", DataType::Utf8, false),
- Field::new("address", DataType::Utf8, false),
- Field::new("priority", DataType::UInt8, false),
- ])
- .with_metadata(metadata2);
-
- // reflexivity
- assert!(schema1.contains(&schema1));
- assert!(schema2.contains(&schema2));
-
- assert!(!schema1.contains(&schema2));
- assert!(schema2.contains(&schema1));
- }
-}
diff --git a/arrow/src/error.rs b/arrow/src/error.rs
index 5d92fb930..f7acec0b3 100644
--- a/arrow/src/error.rs
+++ b/arrow/src/error.rs
@@ -16,120 +16,7 @@
// under the License.
//! Defines `ArrowError` for representing failures in various Arrow operations.
-use std::fmt::{Debug, Display, Formatter};
-use std::io::Write;
-use std::error::Error;
-
-/// Many different operations in the `arrow` crate return this error type.
-#[derive(Debug)]
-pub enum ArrowError {
- /// Returned when functionality is not yet available.
- NotYetImplemented(String),
- ExternalError(Box<dyn Error + Send + Sync>),
- CastError(String),
- MemoryError(String),
- ParseError(String),
- SchemaError(String),
- ComputeError(String),
- DivideByZero,
- CsvError(String),
- JsonError(String),
- IoError(String),
- InvalidArgumentError(String),
- ParquetError(String),
- /// Error during import or export to/from the C Data Interface
- CDataInterface(String),
- DictionaryKeyOverflowError,
-}
-
-impl ArrowError {
- /// Wraps an external error in an `ArrowError`.
- pub fn from_external_error(
- error: Box<dyn ::std::error::Error + Send + Sync>,
- ) -> Self {
- Self::ExternalError(error)
- }
-}
-
-impl From<::std::io::Error> for ArrowError {
- fn from(error: std::io::Error) -> Self {
- ArrowError::IoError(error.to_string())
- }
-}
-
-#[cfg(feature = "csv")]
-impl From<csv_crate::Error> for ArrowError {
- fn from(error: csv_crate::Error) -> Self {
- match error.kind() {
- csv_crate::ErrorKind::Io(error) =>
ArrowError::CsvError(error.to_string()),
- csv_crate::ErrorKind::Utf8 { pos: _, err } =>
ArrowError::CsvError(format!(
- "Encountered UTF-8 error while reading CSV file: {}",
- err
- )),
- csv_crate::ErrorKind::UnequalLengths {
- expected_len, len, ..
- } => ArrowError::CsvError(format!(
- "Encountered unequal lengths between records on CSV file.
Expected {} \
- records, found {} records",
- len, expected_len
- )),
- _ => ArrowError::CsvError("Error reading CSV file".to_string()),
- }
- }
-}
-
-impl From<::std::string::FromUtf8Error> for ArrowError {
- fn from(error: std::string::FromUtf8Error) -> Self {
- ArrowError::ParseError(error.to_string())
- }
-}
-
-#[cfg(feature = "json")]
-impl From<serde_json::Error> for ArrowError {
- fn from(error: serde_json::Error) -> Self {
- ArrowError::JsonError(error.to_string())
- }
-}
-
-impl<W: Write> From<::std::io::IntoInnerError<W>> for ArrowError {
- fn from(error: std::io::IntoInnerError<W>) -> Self {
- ArrowError::IoError(error.to_string())
- }
-}
-
-impl Display for ArrowError {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- match self {
- ArrowError::NotYetImplemented(source) => {
- write!(f, "Not yet implemented: {}", &source)
- }
- ArrowError::ExternalError(source) => write!(f, "External error:
{}", &source),
- ArrowError::CastError(desc) => write!(f, "Cast error: {}", desc),
- ArrowError::MemoryError(desc) => write!(f, "Memory error: {}",
desc),
- ArrowError::ParseError(desc) => write!(f, "Parser error: {}",
desc),
- ArrowError::SchemaError(desc) => write!(f, "Schema error: {}",
desc),
- ArrowError::ComputeError(desc) => write!(f, "Compute error: {}",
desc),
- ArrowError::DivideByZero => write!(f, "Divide by zero error"),
- ArrowError::CsvError(desc) => write!(f, "Csv error: {}", desc),
- ArrowError::JsonError(desc) => write!(f, "Json error: {}", desc),
- ArrowError::IoError(desc) => write!(f, "Io error: {}", desc),
- ArrowError::InvalidArgumentError(desc) => {
- write!(f, "Invalid argument error: {}", desc)
- }
- ArrowError::ParquetError(desc) => {
- write!(f, "Parquet argument error: {}", desc)
- }
- ArrowError::CDataInterface(desc) => {
- write!(f, "C Data interface error: {}", desc)
- }
- ArrowError::DictionaryKeyOverflowError => {
- write!(f, "Dictionary key bigger than the key type")
- }
- }
- }
-}
-
-impl Error for ArrowError {}
+pub use arrow_schema::ArrowError;
pub type Result<T> = std::result::Result<T, ArrowError>;
diff --git a/arrow/src/json/mod.rs b/arrow/src/json/mod.rs
index 836145bb0..21f96d90a 100644
--- a/arrow/src/json/mod.rs
+++ b/arrow/src/json/mod.rs
@@ -80,3 +80,36 @@ impl JsonSerializable for f64 {
Number::from_f64(self).map(Value::Number)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use serde_json::{
+ Number,
+ Value::{Bool, Number as VNumber, String as VString},
+ };
+
+ #[test]
+ fn test_arrow_native_type_to_json() {
+ assert_eq!(Some(Bool(true)), true.into_json_value());
+ assert_eq!(Some(VNumber(Number::from(1))), 1i8.into_json_value());
+ assert_eq!(Some(VNumber(Number::from(1))), 1i16.into_json_value());
+ assert_eq!(Some(VNumber(Number::from(1))), 1i32.into_json_value());
+ assert_eq!(Some(VNumber(Number::from(1))), 1i64.into_json_value());
+ assert_eq!(Some(VString("1".to_string())), 1i128.into_json_value());
+ assert_eq!(Some(VNumber(Number::from(1))), 1u8.into_json_value());
+ assert_eq!(Some(VNumber(Number::from(1))), 1u16.into_json_value());
+ assert_eq!(Some(VNumber(Number::from(1))), 1u32.into_json_value());
+ assert_eq!(Some(VNumber(Number::from(1))), 1u64.into_json_value());
+ assert_eq!(
+ Some(VNumber(Number::from_f64(0.01f64).unwrap())),
+ 0.01.into_json_value()
+ );
+ assert_eq!(
+ Some(VNumber(Number::from_f64(0.01f64).unwrap())),
+ 0.01f64.into_json_value()
+ );
+ assert_eq!(None, f32::NAN.into_json_value());
+ }
+}
diff --git a/arrow/src/json/writer.rs b/arrow/src/json/writer.rs
index bf40b31b4..beee02582 100644
--- a/arrow/src/json/writer.rs
+++ b/arrow/src/json/writer.rs
@@ -700,7 +700,10 @@ where
}
self.format.start_row(&mut self.writer, is_first_row)?;
- self.writer.write_all(&serde_json::to_vec(row)?)?;
+ self.writer.write_all(
+ &serde_json::to_vec(row)
+ .map_err(|error| ArrowError::JsonError(error.to_string()))?,
+ )?;
self.format.end_row(&mut self.writer)?;
Ok(())
}
diff --git a/arrow/src/pyarrow.rs b/arrow/src/pyarrow.rs
index 89463e4c8..90caa2e3a 100644
--- a/arrow/src/pyarrow.rs
+++ b/arrow/src/pyarrow.rs
@@ -39,10 +39,8 @@ use crate::record_batch::RecordBatch;
import_exception!(pyarrow, ArrowException);
pub type PyArrowException = ArrowException;
-impl From<ArrowError> for PyErr {
- fn from(err: ArrowError) -> PyErr {
- PyArrowException::new_err(err.to_string())
- }
+fn to_py_err(err: ArrowError) -> PyErr {
+ PyArrowException::new_err(err.to_string())
}
pub trait PyArrowConvert: Sized {
@@ -55,12 +53,12 @@ impl PyArrowConvert for DataType {
let c_schema = FFI_ArrowSchema::empty();
let c_schema_ptr = &c_schema as *const FFI_ArrowSchema;
value.call_method1("_export_to_c", (c_schema_ptr as Py_uintptr_t,))?;
- let dtype = DataType::try_from(&c_schema)?;
+ let dtype = DataType::try_from(&c_schema).map_err(to_py_err)?;
Ok(dtype)
}
fn to_pyarrow(&self, py: Python) -> PyResult<PyObject> {
- let c_schema = FFI_ArrowSchema::try_from(self)?;
+ let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
let c_schema_ptr = &c_schema as *const FFI_ArrowSchema;
let module = py.import("pyarrow")?;
let class = module.getattr("DataType")?;
@@ -75,12 +73,12 @@ impl PyArrowConvert for Field {
let c_schema = FFI_ArrowSchema::empty();
let c_schema_ptr = &c_schema as *const FFI_ArrowSchema;
value.call_method1("_export_to_c", (c_schema_ptr as Py_uintptr_t,))?;
- let field = Field::try_from(&c_schema)?;
+ let field = Field::try_from(&c_schema).map_err(to_py_err)?;
Ok(field)
}
fn to_pyarrow(&self, py: Python) -> PyResult<PyObject> {
- let c_schema = FFI_ArrowSchema::try_from(self)?;
+ let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
let c_schema_ptr = &c_schema as *const FFI_ArrowSchema;
let module = py.import("pyarrow")?;
let class = module.getattr("Field")?;
@@ -95,12 +93,12 @@ impl PyArrowConvert for Schema {
let c_schema = FFI_ArrowSchema::empty();
let c_schema_ptr = &c_schema as *const FFI_ArrowSchema;
value.call_method1("_export_to_c", (c_schema_ptr as Py_uintptr_t,))?;
- let schema = Schema::try_from(&c_schema)?;
+ let schema = Schema::try_from(&c_schema).map_err(to_py_err)?;
Ok(schema)
}
fn to_pyarrow(&self, py: Python) -> PyResult<PyObject> {
- let c_schema = FFI_ArrowSchema::try_from(self)?;
+ let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
let c_schema_ptr = &c_schema as *const FFI_ArrowSchema;
let module = py.import("pyarrow")?;
let class = module.getattr("Schema")?;
@@ -127,15 +125,17 @@ impl PyArrowConvert for ArrayData {
),
)?;
- let ffi_array =
- unsafe { ffi::ArrowArray::try_from_raw(array_pointer,
schema_pointer)? };
- let data = ArrayData::try_from(ffi_array)?;
+ let ffi_array = unsafe {
+ ffi::ArrowArray::try_from_raw(array_pointer, schema_pointer)
+ .map_err(to_py_err)?
+ };
+ let data = ArrayData::try_from(ffi_array).map_err(to_py_err)?;
Ok(data)
}
fn to_pyarrow(&self, py: Python) -> PyResult<PyObject> {
- let array = ffi::ArrowArray::try_from(self.clone())?;
+ let array =
ffi::ArrowArray::try_from(self.clone()).map_err(to_py_err)?;
let (array_pointer, schema_pointer) = ffi::ArrowArray::into_raw(array);
let module = py.import("pyarrow")?;
@@ -151,6 +151,21 @@ impl PyArrowConvert for ArrayData {
}
}
+impl<T: PyArrowConvert> PyArrowConvert for Vec<T> {
+ fn from_pyarrow(value: &PyAny) -> PyResult<Self> {
+ let list = value.downcast::<PyList>()?;
+ list.iter().map(|x| T::from_pyarrow(&x)).collect()
+ }
+
+ fn to_pyarrow(&self, py: Python) -> PyResult<PyObject> {
+ let values = self
+ .iter()
+ .map(|v| v.to_pyarrow(py))
+ .collect::<PyResult<Vec<_>>>()?;
+ Ok(values.to_object(py))
+ }
+}
+
impl<T> PyArrowConvert for T
where
T: Array + From<ArrayData>,
@@ -176,7 +191,7 @@ impl PyArrowConvert for RecordBatch {
.map(ArrayRef::from_pyarrow)
.collect::<PyResult<_>>()?;
- let batch = RecordBatch::try_new(schema, arrays)?;
+ let batch = RecordBatch::try_new(schema, arrays).map_err(to_py_err)?;
Ok(batch)
}
@@ -237,25 +252,25 @@ impl PyArrowConvert for ArrowArrayStreamReader {
}
}
-macro_rules! add_conversion {
- ($typ:ty) => {
- impl<'source> FromPyObject<'source> for $typ {
- fn extract(value: &'source PyAny) -> PyResult<Self> {
- Self::from_pyarrow(value)
- }
- }
+/// A newtype wrapper around a `T: PyArrowConvert` that implements
+/// [`FromPyObject`] and [`IntoPy`] allowing usage with pyo3 macros
+#[derive(Debug)]
+pub struct PyArrowType<T: PyArrowConvert>(pub T);
- impl<'a> IntoPy<PyObject> for $typ {
- fn into_py(self, py: Python) -> PyObject {
- self.to_pyarrow(py).unwrap()
- }
- }
- };
+impl<'source, T: PyArrowConvert> FromPyObject<'source> for PyArrowType<T> {
+ fn extract(value: &'source PyAny) -> PyResult<Self> {
+ Ok(Self(T::from_pyarrow(value)?))
+ }
+}
+
+impl<'a, T: PyArrowConvert> IntoPy<PyObject> for PyArrowType<T> {
+ fn into_py(self, py: Python) -> PyObject {
+ self.0.to_pyarrow(py).unwrap()
+ }
}
-add_conversion!(DataType);
-add_conversion!(Field);
-add_conversion!(Schema);
-add_conversion!(ArrayData);
-add_conversion!(RecordBatch);
-add_conversion!(ArrowArrayStreamReader);
+impl<T: PyArrowConvert> From<T> for PyArrowType<T> {
+ fn from(s: T) -> Self {
+ Self(s)
+ }
+}
diff --git a/integration-testing/src/util/mod.rs
b/integration-testing/src/util/mod.rs
index 9ecd30136..f9ddc0e6f 100644
--- a/integration-testing/src/util/mod.rs
+++ b/integration-testing/src/util/mod.rs
@@ -265,7 +265,8 @@ impl ArrowJsonField {
/// TODO: convert to use an Into
fn to_arrow_field(&self) -> Result<Field> {
// a bit regressive, but we have to convert the field to JSON in order
to convert it
- let field = serde_json::to_value(self)?;
+ let field = serde_json::to_value(self)
+ .map_err(|error| ArrowError::JsonError(error.to_string()))?;
field_from_json(&field)
}
}