1use std::io::Cursor;
2use std::num::NonZeroUsize;
3
4use num_traits::pow::Pow;
5pub use polars_arrow::array::StructArray;
6use polars_core::prelude::*;
7use polars_core::runtime::RAYON;
8use polars_core::utils::accumulate_dataframes_vertical;
9use polars_json::json::ordered::TapeGuide;
10use rayon::prelude::*;
11
12use crate::RowIndex;
13use crate::mmap::ReaderBytes;
14use crate::ndjson::buffer::*;
15use crate::predicates::PhysicalIoExpr;
16use crate::prelude::*;
17const NEWLINE: u8 = b'\n';
18const CLOSING_BRACKET: u8 = b'}';
19
20pub(crate) struct CoreJsonReader<'a> {
21 reader_bytes: Option<ReaderBytes<'a>>,
22 n_rows: Option<usize>,
23 schema: SchemaRef,
24 n_threads: Option<usize>,
25 sample_size: usize,
26 chunk_size: NonZeroUsize,
27 low_memory: bool,
28 ignore_errors: bool,
29 row_index: Option<&'a mut RowIndex>,
30 predicate: Option<Arc<dyn PhysicalIoExpr>>,
31 projection: Option<Arc<[PlSmallStr]>>,
32}
33impl<'a> CoreJsonReader<'a> {
34 #[allow(clippy::too_many_arguments)]
35 pub(crate) fn new(
36 reader_bytes: ReaderBytes<'a>,
37 n_rows: Option<usize>,
38 schema: Option<SchemaRef>,
39 schema_overwrite: Option<&Schema>,
40 n_threads: Option<usize>,
41 sample_size: usize,
42 chunk_size: NonZeroUsize,
43 low_memory: bool,
44 infer_schema_len: Option<NonZeroUsize>,
45 ignore_errors: bool,
46 row_index: Option<&'a mut RowIndex>,
47 predicate: Option<Arc<dyn PhysicalIoExpr>>,
48 projection: Option<Arc<[PlSmallStr]>>,
49 ) -> PolarsResult<CoreJsonReader<'a>> {
50 let reader_bytes = reader_bytes;
51
52 let mut schema = match schema {
53 Some(schema) => schema,
54 None => {
55 let bytes: &[u8] = &reader_bytes;
56 let mut cursor = Cursor::new(bytes);
57 Arc::new(crate::ndjson::infer_schema(&mut cursor, infer_schema_len)?)
58 },
59 };
60 if let Some(overwriting_schema) = schema_overwrite {
61 let schema = Arc::make_mut(&mut schema);
62 overwrite_schema(schema, overwriting_schema)?;
63 }
64
65 Ok(CoreJsonReader {
66 reader_bytes: Some(reader_bytes),
67 schema,
68 sample_size,
69 n_rows,
70 n_threads,
71 chunk_size,
72 low_memory,
73 ignore_errors,
74 row_index,
75 predicate,
76 projection,
77 })
78 }
79
80 fn parse_json(&mut self, mut n_threads: usize, bytes: &[u8]) -> PolarsResult<DataFrame> {
81 let mut bytes = bytes;
82 let mut total_rows = 128;
83
84 if let Some((mean, std)) = get_line_stats_json(bytes, self.sample_size) {
85 let line_length_upper_bound = mean + 1.1 * std;
86
87 total_rows = (bytes.len() as f32 / (mean - 0.01 * std)) as usize;
88 if let Some(n_rows) = self.n_rows {
89 total_rows = std::cmp::min(n_rows, total_rows);
90 let n_bytes = (line_length_upper_bound * (n_rows as f32)) as usize;
92
93 if n_bytes < bytes.len() {
94 if let Some(pos) = next_line_position_naive_json(&bytes[n_bytes..]) {
95 bytes = &bytes[..n_bytes + pos]
96 }
97 }
98 }
99 }
100
101 if total_rows <= 128 {
102 n_threads = 1;
103 }
104
105 let rows_per_thread = total_rows / n_threads;
106
107 let max_proxy = bytes.len() / n_threads / 2;
108 let capacity = if self.low_memory {
109 usize::from(self.chunk_size)
110 } else {
111 std::cmp::min(rows_per_thread, max_proxy)
112 };
113 let file_chunks = get_file_chunks_json(bytes, n_threads);
114
115 let row_index = self.row_index.as_ref().map(|ri| ri as &RowIndex);
116 let (mut dfs, prepredicate_heights) = RAYON.install(|| {
117 file_chunks
118 .into_par_iter()
119 .map(|(start_pos, stop_at_nbytes)| {
120 let mut local_df = parse_ndjson(
121 &bytes[start_pos..stop_at_nbytes],
122 Some(capacity),
123 &self.schema,
124 self.ignore_errors,
125 )?;
126
127 let prepredicate_height = local_df.height() as IdxSize;
128 if let Some(projection) = self.projection.as_deref() {
129 local_df = local_df.select(projection.iter().cloned())?;
130 }
131
132 if let Some(row_index) = row_index {
133 local_df = local_df
134 .with_row_index(row_index.name.clone(), Some(row_index.offset))?;
135 }
136
137 if let Some(predicate) = &self.predicate {
138 let s = predicate.evaluate_io(&local_df)?;
139 let mask = s.bool()?;
140 local_df = local_df.filter(mask)?;
141 }
142
143 Ok((local_df, prepredicate_height))
144 })
145 .collect::<PolarsResult<(Vec<_>, Vec<_>)>>()
146 })?;
147
148 if let Some(ref mut row_index) = self.row_index {
149 update_row_counts3(&mut dfs, &prepredicate_heights, 0);
150 row_index.offset += prepredicate_heights.iter().copied().sum::<IdxSize>();
151 }
152
153 accumulate_dataframes_vertical(dfs)
154 }
155
156 pub fn as_df(&mut self) -> PolarsResult<DataFrame> {
157 let n_threads = self
158 .n_threads
159 .unwrap_or_else(|| RAYON.current_num_threads());
160
161 let reader_bytes = self.reader_bytes.take().unwrap();
162
163 let mut df = self.parse_json(n_threads, &reader_bytes)?;
164
165 if let Some(n_rows) = self.n_rows {
168 if n_rows < df.height() {
169 df = df.slice(0, n_rows)
170 }
171 }
172 Ok(df)
173 }
174}
175
176#[inline(always)]
177fn parse_impl(
178 bytes: &[u8],
179 buffers: &mut PlIndexMap<BufferKey, Buffer>,
180 scratch: &mut Scratch,
181 guide: Option<&TapeGuide>,
182 ignore_errors: bool,
183) -> PolarsResult<usize> {
184 scratch.json.clear();
185 scratch.json.extend_from_slice(bytes);
186 let n = scratch.json.len();
187 let parse_err = |e| polars_err!(ComputeError: "error parsing line: {}", e);
188 let value = match guide {
189 Some(guide) => {
190 let tape = simd_json::to_tape_with_buffers(&mut scratch.json, &mut scratch.buffers)
191 .map_err(parse_err)?;
192 polars_json::json::ordered::tape_to_value(&tape, guide, ignore_errors)?
193 },
194 None => simd_json::to_borrowed_value_with_buffers(&mut scratch.json, &mut scratch.buffers)
195 .map_err(parse_err)?,
196 };
197 match value {
198 simd_json::BorrowedValue::Object(value) => {
199 buffers.iter_mut().try_for_each(|(s, inner)| {
200 match s.0.map_lookup(&value) {
201 Some(v) => inner.add(v)?,
202 None => inner.add_null(),
203 }
204 PolarsResult::Ok(())
205 })?;
206 },
207 _ if ignore_errors => {
208 buffers.iter_mut().for_each(|(_, inner)| inner.add_null());
209 },
210 v => {
211 polars_bail!(ComputeError: "NDJSON line expected to contain JSON object: {v}");
212 },
213 };
214 Ok(n)
215}
216
217#[derive(Default)]
218struct Scratch {
219 json: Vec<u8>,
220 buffers: simd_json::Buffers,
221}
222
223pub fn json_lines(bytes: &[u8]) -> impl Iterator<Item = &[u8]> {
224 bytes
230 .split(|&byte| byte == b'\n')
231 .filter(|bytes| is_json_line(bytes))
232}
233
234#[inline]
235pub fn is_json_line(bytes: &[u8]) -> bool {
236 bytes
237 .iter()
238 .any(|byte| !matches!(*byte, b' ' | b'\t' | b'\r'))
239}
240
241fn parse_lines(
242 bytes: &[u8],
243 buffers: &mut PlIndexMap<BufferKey, Buffer>,
244 guide: Option<&TapeGuide>,
245 ignore_errors: bool,
246) -> PolarsResult<()> {
247 let mut scratch = Scratch::default();
248
249 let iter = json_lines(bytes);
250 for bytes in iter {
251 parse_impl(bytes, buffers, &mut scratch, guide, ignore_errors)?;
252 }
253 Ok(())
254}
255
256pub fn parse_ndjson(
257 bytes: &[u8],
258 n_rows_hint: Option<usize>,
259 schema: &Schema,
260 ignore_errors: bool,
261) -> PolarsResult<DataFrame> {
262 let capacity = n_rows_hint.unwrap_or_else(|| estimate_n_lines_in_chunk(bytes));
263
264 let has_map = schema.iter_values().any(|dt| dt.contains_map());
266 let guide_dtype = has_map
267 .then(|| DataType::Struct(schema.iter_fields().collect()).to_arrow(CompatLevel::newest()));
268 let guide = guide_dtype.as_ref().map(TapeGuide::new);
269 let decode_schema: Option<Schema> = has_map.then(|| {
270 schema
271 .iter()
272 .map(|(name, dt)| {
273 let dt = if dt.contains_map() {
274 dt.json_decode_dtype()
275 } else {
276 dt.clone()
277 };
278 Field::new(name.clone(), dt)
279 })
280 .collect()
281 });
282
283 let mut buffers = init_buffers(
284 decode_schema.as_ref().unwrap_or(schema),
285 capacity,
286 ignore_errors,
287 )?;
288 parse_lines(bytes, &mut buffers, guide.as_ref(), ignore_errors)?;
289
290 DataFrame::new_infer_height(
291 buffers
292 .into_values()
293 .zip(schema.iter_values())
294 .map(|(buf, dt)| {
295 let s = buf.into_series()?;
296 let s = if dt.contains_map() {
297 s.from_json_decoded(dt, ignore_errors)?
298 } else {
299 s
300 };
301 Ok(s.into_column())
302 })
303 .collect::<PolarsResult<_>>()
304 .map_err(|e| match e {
305 PolarsError::ComputeError(..) => e,
308 PolarsError::SchemaMismatch(e) => PolarsError::ComputeError(e),
309 e => e,
310 })?,
311 )
312}
313
314pub fn estimate_n_lines_in_chunk(chunk: &[u8]) -> usize {
316 chunk
317 .split(|&c| c == b'\n')
318 .find(|x| !x.is_empty())
319 .map_or(1, |x| {
320 chunk.len().div_ceil(
321 x.len().max(
322 chunk
323 .rsplit(|&c| c == b'\n')
324 .find(|x| !x.is_empty())
325 .unwrap()
326 .len(),
327 ),
328 )
329 })
330}
331
332pub(crate) fn next_line_position_naive_json(input: &[u8]) -> Option<usize> {
336 let pos = memchr::memchr(NEWLINE, input)?;
337 if pos == 0 {
338 return Some(1);
339 }
340
341 let is_closing_bracket = input.get(pos - 1) == Some(&CLOSING_BRACKET);
342 if is_closing_bracket {
343 Some(pos + 1)
344 } else {
345 None
346 }
347}
348
349pub(crate) fn get_line_stats_json(bytes: &[u8], n_lines: usize) -> Option<(f32, f32)> {
351 let mut lengths = Vec::with_capacity(n_lines);
352
353 let mut bytes_trunc;
354 let n_lines_per_iter = n_lines / 2;
355
356 let mut n_read = 0;
357
358 let bytes_len = bytes.len();
359
360 for offset in [0, (bytes_len as f32 * 0.75) as usize] {
362 bytes_trunc = &bytes[offset..];
363 let pos = next_line_position_naive_json(bytes_trunc)?;
364 if pos >= bytes_len {
365 return None;
366 }
367 bytes_trunc = &bytes_trunc[pos + 1..];
368
369 for _ in offset..(offset + n_lines_per_iter) {
370 let pos = next_line_position_naive_json(bytes_trunc);
371 if let Some(pos) = pos {
372 lengths.push(pos);
373 let next_bytes = &bytes_trunc[pos..];
374 if next_bytes.is_empty() {
375 return None;
376 }
377 bytes_trunc = next_bytes;
378 n_read += pos;
379 } else {
380 break;
381 }
382 }
383 }
384
385 let n_samples = lengths.len();
386 let mean = (n_read as f32) / (n_samples as f32);
387 let mut std = 0.0;
388 for &len in lengths.iter() {
389 std += (len as f32 - mean).pow(2.0)
390 }
391 std = (std / n_samples as f32).sqrt();
392 Some((mean, std))
393}
394
395pub(crate) fn get_file_chunks_json(bytes: &[u8], n_threads: usize) -> Vec<(usize, usize)> {
396 let mut last_pos = 0;
397 let total_len = bytes.len();
398 let chunk_size = total_len / n_threads;
399 let mut offsets = Vec::with_capacity(n_threads);
400 for _ in 0..n_threads {
401 let search_pos = last_pos + chunk_size;
402
403 if search_pos >= bytes.len() {
404 break;
405 }
406
407 let end_pos = match next_line_position_naive_json(&bytes[search_pos..]) {
408 Some(pos) => search_pos + pos,
409 None => {
410 break;
411 },
412 };
413 offsets.push((last_pos, end_pos));
414 last_pos = end_pos;
415 }
416 offsets.push((last_pos, total_len));
417 offsets
418}