1#![doc(
19 html_logo_url = "https://raw.githubusercontent.com/apache/parquet-format/25f05e73d8cd7f5c83532ce51cb4f4de8ba5f2a2/logo/parquet-logos_1.svg",
20 html_favicon_url = "https://raw.githubusercontent.com/apache/parquet-format/25f05e73d8cd7f5c83532ce51cb4f4de8ba5f2a2/logo/parquet-logos_1.svg"
21)]
22#![cfg_attr(docsrs, feature(doc_cfg))]
23#![allow(clippy::approx_constant)]
24
25use parquet_derive::{ParquetRecordReader, ParquetRecordWriter};
26use std::sync::Arc;
27
28#[derive(ParquetRecordWriter)]
29struct ACompleteRecord<'a> {
30 pub a_bool: bool,
31 pub a_str: &'a str,
32 pub a_string: String,
33 pub a_borrowed_string: &'a String,
34 pub a_arc_str: Arc<str>,
35 pub maybe_a_str: Option<&'a str>,
36 pub maybe_a_string: Option<String>,
37 pub maybe_a_arc_str: Option<Arc<str>>,
38 pub i16: i16,
39 pub i32: i32,
40 pub u64: u64,
41 pub maybe_u8: Option<u8>,
42 pub maybe_i16: Option<i16>,
43 pub maybe_u32: Option<u32>,
44 pub maybe_usize: Option<usize>,
45 pub isize: isize,
46 pub float: f32,
47 pub double: f64,
48 pub maybe_float: Option<f32>,
49 pub maybe_double: Option<f64>,
50 pub borrowed_maybe_a_string: &'a Option<String>,
51 pub borrowed_maybe_a_str: &'a Option<&'a str>,
52 pub now: chrono::NaiveDateTime,
53 pub uuid: uuid::Uuid,
54 pub byte_vec: Vec<u8>,
55 pub maybe_byte_vec: Option<Vec<u8>>,
56 pub borrowed_byte_vec: &'a [u8],
57 pub borrowed_maybe_byte_vec: &'a Option<Vec<u8>>,
58 pub borrowed_maybe_borrowed_byte_vec: &'a Option<&'a [u8]>,
59}
60
61#[derive(PartialEq, ParquetRecordWriter, ParquetRecordReader, Debug)]
62struct APartiallyCompleteRecord {
63 pub bool: bool,
64 pub string: String,
65 pub i16: i16,
66 pub i32: i32,
67 pub u64: u64,
68 pub isize: isize,
69 pub float: f32,
70 pub double: f64,
71 pub now: chrono::NaiveDateTime,
72 pub date: chrono::NaiveDate,
73 pub uuid: uuid::Uuid,
74 pub byte_vec: Vec<u8>,
75}
76
77#[derive(PartialEq, ParquetRecordWriter, Debug)]
81struct APartiallyOptionalRecord {
82 pub bool: bool,
83 pub string: String,
84 pub i16: Option<i16>,
85 pub i32: Option<i32>,
86 pub u64: Option<u64>,
87 pub isize: isize,
88 pub float: f32,
89 pub double: f64,
90 pub now: chrono::NaiveDateTime,
91 pub date: chrono::NaiveDate,
92 pub uuid: uuid::Uuid,
93 pub byte_vec: Vec<u8>,
94}
95
96#[derive(PartialEq, ParquetRecordReader, Debug)]
100struct APrunedRecord {
101 pub bool: bool,
102 pub string: String,
103 pub byte_vec: Vec<u8>,
104 pub float: f32,
105 pub double: f64,
106 pub i16: i16,
107 pub i32: i32,
108 pub u64: u64,
109 pub isize: isize,
110}
111
112#[derive(PartialEq, ParquetRecordWriter, ParquetRecordReader, Debug)]
116struct ARecordWithRawIdentifiers {
117 pub r#type: i32,
118 pub count: i32,
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 use chrono::SubsecRound;
126 use std::{env, fs, io::Write, sync::Arc};
127
128 use parquet::{
129 file::writer::SerializedFileWriter,
130 record::{RecordReader, RecordWriter},
131 schema::parser::parse_message_type,
132 };
133
134 #[test]
135 fn test_parquet_derive_hello() {
136 let file = get_temp_file("test_parquet_derive_hello", &[]);
137
138 let schema_str = "message rust_schema {
141 REQUIRED boolean a_bool;
142 REQUIRED BINARY a_str (STRING);
143 REQUIRED BINARY a_string (STRING);
144 REQUIRED BINARY a_borrowed_string (STRING);
145 REQUIRED BINARY a_arc_str (STRING);
146 OPTIONAL BINARY maybe_a_str (STRING);
147 OPTIONAL BINARY maybe_a_string (STRING);
148 OPTIONAL BINARY maybe_a_arc_str (STRING);
149 REQUIRED INT32 i16 (INTEGER(16,true));
150 REQUIRED INT32 i32;
151 REQUIRED INT64 u64 (INTEGER(64,false));
152 OPTIONAL INT32 maybe_u8 (INTEGER(8,false));
153 OPTIONAL INT32 maybe_i16 (INTEGER(16,true));
154 OPTIONAL INT32 maybe_u32 (INTEGER(32,false));
155 OPTIONAL INT64 maybe_usize (INTEGER(64,false));
156 REQUIRED INT64 isize (INTEGER(64,true));
157 REQUIRED FLOAT float;
158 REQUIRED DOUBLE double;
159 OPTIONAL FLOAT maybe_float;
160 OPTIONAL DOUBLE maybe_double;
161 OPTIONAL BINARY borrowed_maybe_a_string (STRING);
162 OPTIONAL BINARY borrowed_maybe_a_str (STRING);
163 REQUIRED INT64 now (TIMESTAMP_MILLIS);
164 REQUIRED FIXED_LEN_BYTE_ARRAY (16) uuid (UUID);
165 REQUIRED BINARY byte_vec;
166 OPTIONAL BINARY maybe_byte_vec;
167 REQUIRED BINARY borrowed_byte_vec;
168 OPTIONAL BINARY borrowed_maybe_byte_vec;
169 OPTIONAL BINARY borrowed_maybe_borrowed_byte_vec;
170 }";
171
172 let schema = Arc::new(parse_message_type(schema_str).unwrap());
173
174 let a_str = "hello mother".to_owned();
175 let a_borrowed_string = "cool news".to_owned();
176 let a_arc_str: Arc<str> = "hello arc".into();
177 let maybe_a_string = Some("it's true, I'm a string".to_owned());
178 let maybe_a_str = Some(&a_str[..]);
179 let maybe_a_arc_str = Some(a_arc_str.clone());
180 let borrowed_byte_vec = vec![0x68, 0x69, 0x70];
181 let borrowed_maybe_byte_vec = Some(vec![0x71, 0x72]);
182 let borrowed_maybe_borrowed_byte_vec = Some(&borrowed_byte_vec[..]);
183
184 let drs: Vec<ACompleteRecord> = vec![ACompleteRecord {
185 a_bool: true,
186 a_str: &a_str[..],
187 a_string: "hello father".into(),
188 a_borrowed_string: &a_borrowed_string,
189 a_arc_str,
190 maybe_a_str: Some(&a_str[..]),
191 maybe_a_string: Some(a_str.clone()),
192 maybe_a_arc_str,
193 i16: -45,
194 i32: 456,
195 u64: 4563424,
196 maybe_u8: None,
197 maybe_i16: Some(3),
198 maybe_u32: None,
199 maybe_usize: Some(4456),
200 isize: -365,
201 float: 3.5,
202 double: f64::NAN,
203 maybe_float: None,
204 maybe_double: Some(f64::MAX),
205 borrowed_maybe_a_string: &maybe_a_string,
206 borrowed_maybe_a_str: &maybe_a_str,
207 now: chrono::Utc::now().naive_local(),
208 uuid: uuid::Uuid::new_v4(),
209 byte_vec: vec![0x65, 0x66, 0x67],
210 maybe_byte_vec: Some(vec![0x88, 0x89, 0x90]),
211 borrowed_byte_vec: &borrowed_byte_vec,
212 borrowed_maybe_byte_vec: &borrowed_maybe_byte_vec,
213 borrowed_maybe_borrowed_byte_vec: &borrowed_maybe_borrowed_byte_vec,
214 }];
215
216 let generated_schema = drs.as_slice().schema().unwrap();
217
218 assert_eq!(&schema, &generated_schema);
219
220 let props = Default::default();
221 let mut writer = SerializedFileWriter::new(file, generated_schema, props).unwrap();
222
223 let mut row_group = writer.next_row_group().unwrap();
224 drs.as_slice().write_to_row_group(&mut row_group).unwrap();
225 row_group.close().unwrap();
226 writer.close().unwrap();
227 }
228
229 #[test]
230 fn test_parquet_derive_read_write_combined() {
231 let file = get_temp_file("test_parquet_derive_combined", &[]);
232
233 let mut drs: Vec<APartiallyCompleteRecord> = vec![APartiallyCompleteRecord {
234 bool: true,
235 string: "a string".into(),
236 i16: -45,
237 i32: 456,
238 u64: 4563424,
239 isize: -365,
240 float: 3.5,
241 double: f64::NAN,
242 now: chrono::Utc::now().naive_local(),
243 date: chrono::naive::NaiveDate::from_ymd_opt(2015, 3, 14).unwrap(),
244 uuid: uuid::Uuid::new_v4(),
245 byte_vec: vec![0x65, 0x66, 0x67],
246 }];
247
248 let mut out: Vec<APartiallyCompleteRecord> = Vec::new();
249
250 use parquet::file::{reader::FileReader, serialized_reader::SerializedFileReader};
251
252 let generated_schema = drs.as_slice().schema().unwrap();
253
254 let props = Default::default();
255 let mut writer =
256 SerializedFileWriter::new(file.try_clone().unwrap(), generated_schema, props).unwrap();
257
258 let mut row_group = writer.next_row_group().unwrap();
259 drs.as_slice().write_to_row_group(&mut row_group).unwrap();
260 row_group.close().unwrap();
261 writer.close().unwrap();
262
263 let reader = SerializedFileReader::new(file).unwrap();
264
265 let mut row_group = reader.get_row_group(0).unwrap();
266 out.read_from_row_group(&mut *row_group, 1).unwrap();
267
268 drs[0].now = drs[0].now.trunc_subsecs(3);
271
272 assert!(out[0].double.is_nan()); out[0].double = 0.;
274 drs[0].double = 0.;
275
276 assert_eq!(drs[0], out[0]);
277 }
278
279 #[test]
280 fn test_parquet_derive_read_optional_but_valid_column() {
281 let file = get_temp_file("test_parquet_derive_read_optional", &[]);
282 let drs = vec![APartiallyOptionalRecord {
283 bool: true,
284 string: "a string".into(),
285 i16: Some(-45),
286 i32: Some(456),
287 u64: Some(4563424),
288 isize: -365,
289 float: 3.5,
290 double: f64::NAN,
291 now: chrono::Utc::now().naive_local(),
292 date: chrono::naive::NaiveDate::from_ymd_opt(2015, 3, 14).unwrap(),
293 uuid: uuid::Uuid::new_v4(),
294 byte_vec: vec![0x65, 0x66, 0x67],
295 }];
296
297 let generated_schema = drs.as_slice().schema().unwrap();
298
299 let props = Default::default();
300 let mut writer =
301 SerializedFileWriter::new(file.try_clone().unwrap(), generated_schema, props).unwrap();
302
303 let mut row_group = writer.next_row_group().unwrap();
304 drs.as_slice().write_to_row_group(&mut row_group).unwrap();
305 row_group.close().unwrap();
306 writer.close().unwrap();
307
308 use parquet::file::{reader::FileReader, serialized_reader::SerializedFileReader};
309 let reader = SerializedFileReader::new(file).unwrap();
310 let mut out: Vec<APartiallyCompleteRecord> = Vec::new();
311
312 let mut row_group = reader.get_row_group(0).unwrap();
313 out.read_from_row_group(&mut *row_group, 1).unwrap();
314
315 assert_eq!(drs[0].i16.unwrap(), out[0].i16);
316 assert_eq!(drs[0].i32.unwrap(), out[0].i32);
317 assert_eq!(drs[0].u64.unwrap(), out[0].u64);
318 }
319
320 #[test]
321 fn test_parquet_derive_read_pruned_and_shuffled_columns() {
322 let file = get_temp_file("test_parquet_derive_read_pruned", &[]);
323 let drs = vec![APartiallyCompleteRecord {
324 bool: true,
325 string: "a string".into(),
326 i16: -45,
327 i32: 456,
328 u64: 4563424,
329 isize: -365,
330 float: 3.5,
331 double: f64::NAN,
332 now: chrono::Utc::now().naive_local(),
333 date: chrono::naive::NaiveDate::from_ymd_opt(2015, 3, 14).unwrap(),
334 uuid: uuid::Uuid::new_v4(),
335 byte_vec: vec![0x65, 0x66, 0x67],
336 }];
337
338 let generated_schema = drs.as_slice().schema().unwrap();
339
340 let props = Default::default();
341 let mut writer =
342 SerializedFileWriter::new(file.try_clone().unwrap(), generated_schema, props).unwrap();
343
344 let mut row_group = writer.next_row_group().unwrap();
345 drs.as_slice().write_to_row_group(&mut row_group).unwrap();
346 row_group.close().unwrap();
347 writer.close().unwrap();
348
349 use parquet::file::{reader::FileReader, serialized_reader::SerializedFileReader};
350 let reader = SerializedFileReader::new(file).unwrap();
351 let mut out: Vec<APrunedRecord> = Vec::new();
352
353 let mut row_group = reader.get_row_group(0).unwrap();
354 out.read_from_row_group(&mut *row_group, 1).unwrap();
355
356 assert_eq!(drs[0].bool, out[0].bool);
357 assert_eq!(drs[0].string, out[0].string);
358 assert_eq!(drs[0].byte_vec, out[0].byte_vec);
359 assert_eq!(drs[0].float, out[0].float);
360 assert!(drs[0].double.is_nan());
361 assert!(out[0].double.is_nan());
362 assert_eq!(drs[0].i16, out[0].i16);
363 assert_eq!(drs[0].i32, out[0].i32);
364 assert_eq!(drs[0].u64, out[0].u64);
365 assert_eq!(drs[0].isize, out[0].isize);
366 }
367
368 #[test]
369 fn test_parquet_derive_raw_identifiers() {
370 let file = get_temp_file("test_parquet_derive_raw_identifiers", &[]);
371 let drs = vec![ARecordWithRawIdentifiers {
372 r#type: 456,
373 count: 123,
374 }];
375
376 let generated_schema = drs.as_slice().schema().unwrap();
377
378 assert_eq!(
381 vec!["type", "count"],
382 generated_schema
383 .get_fields()
384 .iter()
385 .map(|field| field.name())
386 .collect::<Vec<_>>()
387 );
388
389 let props = Default::default();
390 let mut writer =
391 SerializedFileWriter::new(file.try_clone().unwrap(), generated_schema, props).unwrap();
392
393 let mut row_group = writer.next_row_group().unwrap();
394 drs.as_slice().write_to_row_group(&mut row_group).unwrap();
395 row_group.close().unwrap();
396 writer.close().unwrap();
397
398 use parquet::file::{reader::FileReader, serialized_reader::SerializedFileReader};
399 let reader = SerializedFileReader::new(file).unwrap();
400 let mut out: Vec<ARecordWithRawIdentifiers> = Vec::new();
401
402 let mut row_group = reader.get_row_group(0).unwrap();
403 out.read_from_row_group(&mut *row_group, 1).unwrap();
404
405 assert_eq!(drs, out);
406 }
407
408 #[test]
409 fn test_aliased_result() {
410 mod aliased_result {
414 use parquet_derive::{ParquetRecordReader, ParquetRecordWriter};
415
416 pub type Result = ();
422
423 #[derive(ParquetRecordReader, ParquetRecordWriter, Debug)]
424 pub struct ARecord {
425 pub bool: bool,
426 pub string: String,
427 }
428
429 impl ARecord {
430 pub fn do_nothing(&self) -> Result {}
431 pub fn validate(&self) -> std::result::Result<(), Box<dyn std::error::Error>> {
432 Ok(())
433 }
434 }
435 }
436
437 use aliased_result::ARecord;
438 let foo = ARecord {
439 bool: true,
440 string: "test".to_string(),
441 };
442 foo.do_nothing();
443 assert!(foo.validate().is_ok());
444 }
445
446 pub fn get_temp_file(file_name: &str, content: &[u8]) -> fs::File {
448 let mut path_buf = env::current_dir().unwrap();
450 path_buf.push("target");
451 path_buf.push("debug");
452 path_buf.push("testdata");
453 fs::create_dir_all(&path_buf).unwrap();
454 path_buf.push(file_name);
455
456 let mut tmp_file = fs::File::create(path_buf.as_path()).unwrap();
458 tmp_file.write_all(content).unwrap();
459 tmp_file.sync_all().unwrap();
460
461 let file = fs::OpenOptions::new()
463 .read(true)
464 .write(true)
465 .open(path_buf.as_path());
466 assert!(file.is_ok());
467 file.unwrap()
468 }
469}