arrow_integration_testing/
lib.rs1#![allow(unused_crate_dependencies)]
22#![warn(missing_docs)]
23use serde_json::Value;
24
25use arrow::array::{Array, StructArray};
26use arrow::datatypes::{DataType, Field, Fields, Schema};
27use arrow::error::{ArrowError, Result};
28use arrow::ffi::{FFI_ArrowArray, FFI_ArrowSchema, from_ffi_and_data_type};
29use arrow::record_batch::RecordBatch;
30use arrow::util::test_util::arrow_test_data;
31use arrow_integration_test::*;
32use std::collections::HashMap;
33use std::ffi::{CStr, CString, c_char, c_int};
34use std::fs::File;
35use std::io::BufReader;
36use std::iter::zip;
37use std::ptr;
38use std::sync::Arc;
39
40pub const AUTH_USERNAME: &str = "arrow";
42pub const AUTH_PASSWORD: &str = "flight";
44
45pub mod flight_client_scenarios;
46pub mod flight_server_scenarios;
47
48pub struct ArrowFile {
50 pub schema: Schema,
52 dictionaries: HashMap<i64, ArrowJsonDictionaryBatch>,
55 arrow_json: Value,
56}
57
58impl ArrowFile {
59 pub fn read_batch(&self, batch_num: usize) -> Result<RecordBatch> {
61 let b = self.arrow_json["batches"].get(batch_num).unwrap();
62 let json_batch: ArrowJsonBatch = serde_json::from_value(b.clone()).unwrap();
63 record_batch_from_json(&self.schema, json_batch, Some(&self.dictionaries))
64 }
65
66 pub fn read_batches(&self) -> Result<Vec<RecordBatch>> {
68 self.arrow_json["batches"]
69 .as_array()
70 .unwrap()
71 .iter()
72 .map(|b| {
73 let json_batch: ArrowJsonBatch = serde_json::from_value(b.clone()).unwrap();
74 record_batch_from_json(&self.schema, json_batch, Some(&self.dictionaries))
75 })
76 .collect()
77 }
78}
79
80pub fn canonicalize_schema(schema: &Schema) -> Schema {
82 let fields = schema
83 .fields()
84 .iter()
85 .map(|field| match field.data_type() {
86 DataType::Map(child_field, sorted) => match child_field.data_type() {
87 DataType::Struct(fields) if fields.len() == 2 => {
88 let first_field = &fields[0];
89 let key_field = Arc::new(Field::new(
90 Field::MAP_KEY_FIELD_DEFAULT_NAME,
91 first_field.data_type().clone(),
92 false,
93 ));
94 let second_field = &fields[1];
95 let value_field = Arc::new(Field::new(
96 Field::MAP_VALUE_FIELD_DEFAULT_NAME,
97 second_field.data_type().clone(),
98 second_field.is_nullable(),
99 ));
100
101 let fields = Fields::from([key_field, value_field]);
102 let struct_type = DataType::Struct(fields);
103 let child_field =
104 Field::new(Field::MAP_ENTRIES_FIELD_DEFAULT_NAME, struct_type, false);
105
106 Arc::new(Field::new(
107 field.name().as_str(),
108 DataType::Map(Arc::new(child_field), *sorted),
109 field.is_nullable(),
110 ))
111 }
112 _ => panic!("The child field of Map type should be Struct type with 2 fields."),
113 },
114 _ => field.clone(),
115 })
116 .collect::<Fields>();
117
118 Schema::new(fields).with_metadata(schema.metadata().clone())
119}
120
121pub fn open_json_file(json_name: &str) -> Result<ArrowFile> {
123 let json_file = File::open(json_name)?;
124 let reader = BufReader::new(json_file);
125 let arrow_json: Value = serde_json::from_reader(reader).unwrap();
126 let schema = schema_from_json(&arrow_json["schema"])?;
127 let mut dictionaries = HashMap::new();
129 if let Some(dicts) = arrow_json.get("dictionaries") {
130 for d in dicts
131 .as_array()
132 .expect("Unable to get dictionaries as array")
133 {
134 let json_dict: ArrowJsonDictionaryBatch =
135 serde_json::from_value(d.clone()).expect("Unable to get dictionary from JSON");
136 dictionaries.insert(json_dict.id, json_dict);
138 }
139 }
140 Ok(ArrowFile {
141 schema,
142 dictionaries,
143 arrow_json,
144 })
145}
146
147pub fn read_gzip_json(version: &str, path: &str) -> ArrowJson {
156 use flate2::read::GzDecoder;
157 use std::io::Read;
158
159 let testdata = arrow_test_data();
160 let file = File::open(format!(
161 "{testdata}/arrow-ipc-stream/integration/{version}/{path}.json.gz"
162 ))
163 .unwrap();
164 let mut gz = GzDecoder::new(&file);
165 let mut s = String::new();
166 gz.read_to_string(&mut s).unwrap();
167 let arrow_json: ArrowJson = serde_json::from_str(&s).unwrap();
169 arrow_json
170}
171
172fn cdata_integration_export_schema_from_json(
174 c_json_name: *const c_char,
175 out: *mut FFI_ArrowSchema,
176) -> Result<()> {
177 let json_name = unsafe { CStr::from_ptr(c_json_name) };
178 let f = open_json_file(json_name.to_str()?)?;
179 let c_schema = FFI_ArrowSchema::try_from(&f.schema)?;
180 unsafe { ptr::write(out, c_schema) };
182 Ok(())
183}
184
185fn cdata_integration_export_batch_from_json(
187 c_json_name: *const c_char,
188 batch_num: c_int,
189 out: *mut FFI_ArrowArray,
190) -> Result<()> {
191 let json_name = unsafe { CStr::from_ptr(c_json_name) };
192 let b = open_json_file(json_name.to_str()?)?.read_batch(batch_num.try_into().unwrap())?;
193 let a = StructArray::from(b).into_data();
194 let c_array = FFI_ArrowArray::new(&a);
195 unsafe { ptr::write(out, c_array) };
197 Ok(())
198}
199
200fn cdata_integration_import_schema_and_compare_to_json(
201 c_json_name: *const c_char,
202 c_schema: *mut FFI_ArrowSchema,
203) -> Result<()> {
204 let json_name = unsafe { CStr::from_ptr(c_json_name) };
205 let json_schema = open_json_file(json_name.to_str()?)?.schema;
206
207 let imported_schema = unsafe { FFI_ArrowSchema::from_raw(c_schema) };
209 let imported_schema = Schema::try_from(&imported_schema)?;
210
211 if canonicalize_schema(&json_schema) != canonicalize_schema(&imported_schema) {
213 return Err(ArrowError::ComputeError(format!(
214 "Schemas do not match.\n- JSON: {json_schema:?}\n- Imported: {imported_schema:?}",
215 )));
216 }
217 Ok(())
218}
219
220fn compare_batches(a: &RecordBatch, b: &RecordBatch) -> Result<()> {
221 if a.num_columns() != b.num_columns() {
222 return Err(ArrowError::InvalidArgumentError(
223 "batches do not have the same number of columns".to_string(),
224 ));
225 }
226 for (a_column, b_column) in zip(a.columns(), b.columns()) {
227 if a_column != b_column {
228 return Err(ArrowError::InvalidArgumentError(
229 "batch columns are not the same".to_string(),
230 ));
231 }
232 }
233 Ok(())
234}
235
236fn cdata_integration_import_batch_and_compare_to_json(
237 c_json_name: *const c_char,
238 batch_num: c_int,
239 c_array: *mut FFI_ArrowArray,
240) -> Result<()> {
241 let json_name = unsafe { CStr::from_ptr(c_json_name) };
242 let json_batch =
243 open_json_file(json_name.to_str()?)?.read_batch(batch_num.try_into().unwrap())?;
244 let schema = json_batch.schema();
245
246 let data_type_for_import = DataType::Struct(schema.fields.clone());
247 let imported_array = unsafe { FFI_ArrowArray::from_raw(c_array) };
248 let imported_array = unsafe { from_ffi_and_data_type(imported_array, data_type_for_import) }?;
249 imported_array.validate_full()?;
250 let imported_batch = RecordBatch::from(StructArray::from(imported_array));
251
252 compare_batches(&json_batch, &imported_batch)
253}
254
255fn result_to_c_error<T, E: std::fmt::Display>(result: &std::result::Result<T, E>) -> *mut c_char {
257 match result {
258 Ok(_) => ptr::null_mut(),
259 Err(e) => CString::new(format!("{e}")).unwrap().into_raw(),
260 }
261}
262
263#[unsafe(no_mangle)]
269pub unsafe extern "C" fn arrow_rs_free_error(c_error: *mut c_char) {
270 if !c_error.is_null() {
271 drop(unsafe { CString::from_raw(c_error) });
272 }
273}
274
275#[unsafe(no_mangle)]
277pub extern "C" fn arrow_rs_cdata_integration_export_schema_from_json(
278 c_json_name: *const c_char,
279 out: *mut FFI_ArrowSchema,
280) -> *mut c_char {
281 let r = cdata_integration_export_schema_from_json(c_json_name, out);
282 result_to_c_error(&r)
283}
284
285#[unsafe(no_mangle)]
287pub extern "C" fn arrow_rs_cdata_integration_import_schema_and_compare_to_json(
288 c_json_name: *const c_char,
289 c_schema: *mut FFI_ArrowSchema,
290) -> *mut c_char {
291 let r = cdata_integration_import_schema_and_compare_to_json(c_json_name, c_schema);
292 result_to_c_error(&r)
293}
294
295#[unsafe(no_mangle)]
297pub extern "C" fn arrow_rs_cdata_integration_export_batch_from_json(
298 c_json_name: *const c_char,
299 batch_num: c_int,
300 out: *mut FFI_ArrowArray,
301) -> *mut c_char {
302 let r = cdata_integration_export_batch_from_json(c_json_name, batch_num, out);
303 result_to_c_error(&r)
304}
305
306#[unsafe(no_mangle)]
308pub extern "C" fn arrow_rs_cdata_integration_import_batch_and_compare_to_json(
309 c_json_name: *const c_char,
310 batch_num: c_int,
311 c_array: *mut FFI_ArrowArray,
312) -> *mut c_char {
313 let r = cdata_integration_import_batch_and_compare_to_json(c_json_name, batch_num, c_array);
314 result_to_c_error(&r)
315}