1use std::{cmp::min, collections::VecDeque, num::NonZero, ops::Range, sync::atomic::AtomicU64};
5
6use byteorder::{ByteOrder, LittleEndian};
7use bytes::{Buf, Bytes};
8use futures::{Stream, StreamExt, TryStreamExt};
9use lance_core::deepsize::DeepSizeOf;
10use prost::Message;
11use serde::{Deserialize, Serialize};
12
13use crate::traits::{ProtoStruct, Reader};
14use lance_core::{Error, Result};
15
16pub mod tracking_store;
17
18pub const METADATA_READ_CHUNK_SIZE: usize = 16 * 1024 * 1024;
28
29pub fn read_range_in_chunks(
35 reader: &dyn Reader,
36 range: Range<usize>,
37 chunk_size: usize,
38) -> impl Stream<Item = object_store::Result<Bytes>> + '_ {
39 let end = range.end;
40 let chunk_ranges = range
41 .step_by(chunk_size)
42 .map(move |start| start..min(start + chunk_size, end));
43 futures::stream::iter(chunk_ranges.map(|chunk| reader.get_range(chunk)))
44 .buffered(reader.io_parallelism().max(1))
45}
46
47#[derive(Debug, Default)]
57pub struct ChunkedBuf {
58 chunks: VecDeque<Bytes>,
59 remaining: usize,
60}
61
62impl ChunkedBuf {
63 pub fn push(&mut self, chunk: Bytes) {
66 if !chunk.is_empty() {
67 self.remaining += chunk.len();
68 self.chunks.push_back(chunk);
69 }
70 }
71
72 pub fn truncate(&mut self, len: usize) {
78 if len >= self.remaining {
79 return;
80 }
81 let mut to_drop = self.remaining - len;
82 while to_drop > 0 {
83 let Some(back) = self.chunks.back_mut() else {
84 break;
85 };
86 if to_drop >= back.len() {
87 to_drop -= back.len();
88 self.chunks.pop_back();
89 } else {
90 back.truncate(back.len() - to_drop);
91 to_drop = 0;
92 }
93 }
94 self.remaining = len;
95 }
96}
97
98impl Buf for ChunkedBuf {
99 fn remaining(&self) -> usize {
100 self.remaining
101 }
102
103 fn chunk(&self) -> &[u8] {
104 self.chunks.front().map(Bytes::as_ref).unwrap_or(&[])
105 }
106
107 fn advance(&mut self, mut cnt: usize) {
108 assert!(
109 cnt <= self.remaining,
110 "cannot advance ChunkedBuf by {cnt} bytes: only {} remain",
111 self.remaining
112 );
113 self.remaining -= cnt;
114 while cnt > 0 {
115 let front = self
116 .chunks
117 .front_mut()
118 .expect("remaining bytes imply a chunk");
119 if cnt < front.len() {
120 front.advance(cnt);
121 return;
122 }
123 cnt -= front.len();
124 self.chunks.pop_front();
125 }
126 }
127
128 fn copy_to_bytes(&mut self, len: usize) -> Bytes {
129 assert!(
130 len <= self.remaining,
131 "cannot copy {len} bytes from ChunkedBuf: only {} remain",
132 self.remaining
133 );
134 let within_front = self.chunks.front().is_some_and(|front| len <= front.len());
135 if within_front {
136 let front = self
137 .chunks
138 .front_mut()
139 .expect("checked that a front chunk exists");
140 let bytes = front.split_to(len);
141 self.remaining -= len;
142 if front.is_empty() {
143 self.chunks.pop_front();
144 }
145 return bytes;
146 }
147 let mut out = Vec::with_capacity(len);
149 let mut left = len;
150 while left > 0 {
151 let chunk = self.chunk();
152 let take = min(left, chunk.len());
153 out.extend_from_slice(&chunk[..take]);
154 self.advance(take);
155 left -= take;
156 }
157 Bytes::from(out)
158 }
159}
160
161pub async fn read_message<M: Message + Default>(reader: &dyn Reader, pos: usize) -> Result<M> {
166 let file_size = reader.size().await?;
167 if pos + 4 > file_size {
172 return Err(Error::io("file size is too small".to_string()));
173 }
174
175 let range = pos..min(pos + reader.block_size(), file_size);
176 let buf = reader.get_range(range.clone()).await?;
177 let msg_len = LittleEndian::read_u32(&buf) as usize;
178
179 if msg_len + 4 > buf.len() {
180 let remaining_range = range.end..min(4 + pos + msg_len, file_size);
181 let mut full = ChunkedBuf::default();
186 full.push(buf.slice(4..));
187 let mut chunks = read_range_in_chunks(reader, remaining_range, METADATA_READ_CHUNK_SIZE);
188 while let Some(chunk) = chunks.try_next().await? {
189 full.push(chunk);
190 }
191 if full.remaining() < msg_len {
192 return Err(Error::io("file size is too small".to_string()));
193 }
194 full.truncate(msg_len);
195 Ok(M::decode(full)?)
196 } else {
197 Ok(M::decode(buf.slice(4..4 + msg_len))?)
198 }
199}
200
201pub async fn read_struct<
204 M: Message + Default + 'static,
205 T: ProtoStruct<Proto = M> + TryFrom<M, Error = Error>,
206>(
207 reader: &dyn Reader,
208 pos: usize,
209) -> Result<T> {
210 let msg = read_message::<M>(reader, pos).await?;
211 T::try_from(msg)
212}
213
214pub async fn read_last_block(reader: &dyn Reader) -> object_store::Result<Bytes> {
215 let file_size = reader.size().await?;
216 let block_size = reader.block_size();
217 let begin = file_size.saturating_sub(block_size);
218 reader.get_range(begin..file_size).await
219}
220
221pub fn read_metadata_offset(bytes: &Bytes) -> Result<usize> {
222 let len = bytes.len();
223 if len < 16 {
224 return Err(Error::io(format!(
225 "does not have sufficient data, len: {}, bytes: {:?}",
226 len, bytes
227 )));
228 }
229 let offset_bytes = bytes.slice(len - 16..len - 8);
230 Ok(LittleEndian::read_u64(offset_bytes.as_ref()) as usize)
231}
232
233pub fn read_version(bytes: &Bytes) -> Result<(u16, u16)> {
235 let len = bytes.len();
236 if len < 8 {
237 return Err(Error::io(format!(
238 "does not have sufficient data, len: {}, bytes: {:?}",
239 len, bytes
240 )));
241 }
242
243 let major_version = LittleEndian::read_u16(bytes.slice(len - 8..len - 6).as_ref());
244 let minor_version = LittleEndian::read_u16(bytes.slice(len - 6..len - 4).as_ref());
245 Ok((major_version, minor_version))
246}
247
248pub fn read_message_from_buf<M: Message + Default>(buf: &Bytes) -> Result<M> {
250 let msg_len = LittleEndian::read_u32(buf) as usize;
251 Ok(M::decode(&buf[4..4 + msg_len])?)
252}
253
254pub fn read_struct_from_buf<
256 M: Message + Default,
257 T: ProtoStruct<Proto = M> + TryFrom<M, Error = Error>,
258>(
259 buf: &Bytes,
260) -> Result<T> {
261 let msg: M = read_message_from_buf(buf)?;
262 T::try_from(msg)
263}
264
265#[derive(Debug, DeepSizeOf)]
272pub struct CachedFileSize(AtomicU64);
273
274impl<'de> Deserialize<'de> for CachedFileSize {
275 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
276 where
277 D: serde::Deserializer<'de>,
278 {
279 let size = Option::<u64>::deserialize(deserializer)?.unwrap_or(0);
280 Ok(Self::new(size))
281 }
282}
283
284impl Serialize for CachedFileSize {
285 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
286 where
287 S: serde::Serializer,
288 {
289 let size = self.0.load(std::sync::atomic::Ordering::Relaxed);
290 if size == 0 {
291 serializer.serialize_none()
292 } else {
293 serializer.serialize_u64(size)
294 }
295 }
296}
297
298impl From<Option<NonZero<u64>>> for CachedFileSize {
299 fn from(size: Option<NonZero<u64>>) -> Self {
300 match size {
301 Some(size) => Self(AtomicU64::new(size.into())),
302 None => Self(AtomicU64::new(0)),
303 }
304 }
305}
306
307impl Default for CachedFileSize {
308 fn default() -> Self {
309 Self(AtomicU64::new(0))
310 }
311}
312
313impl Clone for CachedFileSize {
314 fn clone(&self) -> Self {
315 Self(AtomicU64::new(
316 self.0.load(std::sync::atomic::Ordering::Relaxed),
317 ))
318 }
319}
320
321impl PartialEq for CachedFileSize {
322 fn eq(&self, other: &Self) -> bool {
323 self.0.load(std::sync::atomic::Ordering::Relaxed)
324 == other.0.load(std::sync::atomic::Ordering::Relaxed)
325 }
326}
327
328impl Eq for CachedFileSize {}
329
330impl CachedFileSize {
331 pub fn new(size: u64) -> Self {
336 Self(AtomicU64::new(size))
337 }
338
339 pub fn unknown() -> Self {
340 Self(AtomicU64::new(0))
341 }
342
343 pub fn get(&self) -> Option<NonZero<u64>> {
344 NonZero::new(self.0.load(std::sync::atomic::Ordering::Relaxed))
345 }
346
347 pub fn set(&self, size: NonZero<u64>) {
348 self.0
349 .store(size.into(), std::sync::atomic::Ordering::Relaxed);
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use std::cmp::min;
356
357 use bytes::{Buf, Bytes, BytesMut};
358 use futures::TryStreamExt;
359 use object_store::path::Path;
360 use prost::Message;
361
362 use super::{ChunkedBuf, read_message};
363
364 use crate::{
365 Error, Result,
366 object_reader::CloudObjectReader,
367 object_store::{DEFAULT_DOWNLOAD_RETRY_COUNT, ObjectStore},
368 object_writer::ObjectWriter,
369 traits::{ProtoStruct, WriteExt, Writer},
370 utils::{METADATA_READ_CHUNK_SIZE, read_range_in_chunks, read_struct},
371 };
372
373 #[derive(Debug, PartialEq)]
376 struct BytesWrapper(Bytes);
377
378 impl ProtoStruct for BytesWrapper {
379 type Proto = Bytes;
380 }
381
382 impl From<&BytesWrapper> for Bytes {
383 fn from(value: &BytesWrapper) -> Self {
384 value.0.clone()
385 }
386 }
387
388 impl TryFrom<Bytes> for BytesWrapper {
389 type Error = Error;
390 fn try_from(value: Bytes) -> Result<Self> {
391 Ok(Self(value))
392 }
393 }
394
395 #[tokio::test]
396 async fn test_write_proto_structs() {
397 let store = ObjectStore::memory();
398 let path = Path::from("/foo");
399
400 let mut object_writer = ObjectWriter::new(&store, &path).await.unwrap();
401 assert_eq!(object_writer.tell().await.unwrap(), 0);
402
403 let some_message = BytesWrapper(Bytes::from(vec![10, 20, 30]));
404
405 let pos = object_writer.write_struct(&some_message).await.unwrap();
406 assert_eq!(pos, 0);
407 object_writer.shutdown().await.unwrap();
408
409 let object_reader =
410 CloudObjectReader::new(store.inner, path, 1024, None, DEFAULT_DOWNLOAD_RETRY_COUNT)
411 .unwrap();
412 let actual: BytesWrapper = read_struct(&object_reader, pos).await.unwrap();
413 assert_eq!(some_message, actual);
414 }
415
416 #[tokio::test]
417 async fn test_read_range_in_chunks_reassembles_in_order() {
418 let store = ObjectStore::memory();
419 let path = Path::from("/chunked");
420 let data: Vec<u8> = (0..10 * 1024 + 37).map(|i| (i % 251) as u8).collect();
423 store.put(&path, &data).await.unwrap();
424 let reader = store.open(&path).await.unwrap();
425
426 let range = 5..data.len() - 3;
427 let mut assembled = BytesMut::new();
428 let mut chunks = read_range_in_chunks(reader.as_ref(), range.clone(), 1024);
429 while let Some(chunk) = chunks.try_next().await.unwrap() {
430 assembled.extend_from_slice(&chunk);
431 }
432 assert_eq!(assembled.as_ref(), &data[range]);
433 }
434
435 #[tokio::test]
436 async fn test_read_range_in_chunks_zero_parallelism_reader() {
437 let store = ObjectStore::memory();
440 let path = Path::from("/zero_parallelism");
441 let data: Vec<u8> = (0..4096).map(|i| (i % 249) as u8).collect();
442 store.put(&path, &data).await.unwrap();
443 let reader =
444 CloudObjectReader::new(store.inner, path, 1024, None, DEFAULT_DOWNLOAD_RETRY_COUNT)
445 .unwrap()
446 .with_io_parallelism(0);
447
448 let assembled = tokio::time::timeout(std::time::Duration::from_secs(5), async {
449 let mut buf = BytesMut::new();
450 let mut chunks = read_range_in_chunks(&reader, 0..data.len(), 1024);
451 while let Some(chunk) = chunks.try_next().await.unwrap() {
452 buf.extend_from_slice(&chunk);
453 }
454 buf
455 })
456 .await
457 .expect("chunked read with a zero-parallelism reader must not hang");
458 assert_eq!(assembled.as_ref(), &data[..]);
459 }
460
461 #[tokio::test]
462 async fn test_read_message_larger_than_chunk_size() {
463 let store = ObjectStore::memory();
466 let path = Path::from("/large_message");
467
468 let mut object_writer = ObjectWriter::new(&store, &path).await.unwrap();
469 let payload: Vec<u8> = (0..METADATA_READ_CHUNK_SIZE + 5 * 1024 * 1024)
470 .map(|i| (i % 253) as u8)
471 .collect();
472 let message = BytesWrapper(Bytes::from(payload));
473 let pos = object_writer.write_struct(&message).await.unwrap();
474 object_writer.shutdown().await.unwrap();
475
476 let object_reader =
477 CloudObjectReader::new(store.inner, path, 4096, None, DEFAULT_DOWNLOAD_RETRY_COUNT)
478 .unwrap();
479 let actual: BytesWrapper = read_struct(&object_reader, pos).await.unwrap();
480 assert_eq!(message, actual);
481 }
482
483 #[tokio::test]
484 async fn test_copy_reader_to_writer() {
485 let store = ObjectStore::memory();
486 let src = Path::from("/src");
487 let dst = Path::from("/dst");
488 store.put(&src, b"abcdef").await.unwrap();
489
490 let reader = store.open(&src).await.unwrap();
491 let mut writer = store.create(&dst).await.unwrap();
492 let copied = writer.copy_from_reader(reader.as_ref()).await.unwrap();
493 writer.shutdown().await.unwrap();
494
495 assert_eq!(copied, 6);
496 assert_eq!(store.read_one_all(&dst).await.unwrap().as_ref(), b"abcdef");
497 }
498
499 #[tokio::test]
500 async fn test_copy_reader_range_to_writer() {
501 let store = ObjectStore::memory();
502 let src = Path::from("/src-range");
503 let dst = Path::from("/dst-range");
504 store.put(&src, b"abcdef").await.unwrap();
505
506 let reader = store.open(&src).await.unwrap();
507 let mut writer = store.create(&dst).await.unwrap();
508 let copied = writer
509 .copy_range_from_reader(reader.as_ref(), 2..5)
510 .await
511 .unwrap();
512 writer.shutdown().await.unwrap();
513
514 assert_eq!(copied, 3);
515 assert_eq!(store.read_one_all(&dst).await.unwrap().as_ref(), b"cde");
516 }
517
518 #[test]
519 fn chunked_buf_advances_and_copies_across_chunks() {
520 let mut buf = ChunkedBuf::default();
521 buf.push(Bytes::from_static(b"abc"));
522 buf.push(Bytes::new());
523 buf.push(Bytes::from_static(b"defgh"));
524 buf.push(Bytes::from_static(b"ij"));
525 assert_eq!(buf.remaining(), 10);
526 assert_eq!(buf.chunk(), b"abc");
527
528 let first_chunk_ptr = buf.chunk().as_ptr();
530 let head = buf.copy_to_bytes(2);
531 assert_eq!(head.as_ref(), b"ab");
532 assert_eq!(head.as_ptr(), first_chunk_ptr);
533
534 let cross = buf.copy_to_bytes(4);
536 assert_eq!(cross.as_ref(), b"cdef");
537 assert_eq!(buf.remaining(), 4);
538 assert_eq!(buf.chunk(), b"gh");
539
540 buf.advance(3);
541 assert_eq!(buf.chunk(), b"j");
542 assert_eq!(buf.copy_to_bytes(1).as_ref(), b"j");
543 assert_eq!(buf.remaining(), 0);
544 assert_eq!(buf.chunk(), b"");
545 }
546
547 #[test]
548 fn chunked_buf_truncate_drops_trailing_bytes() {
549 let mut buf = ChunkedBuf::default();
550 buf.push(Bytes::from_static(b"0123"));
551 buf.push(Bytes::from_static(b"4567"));
552 buf.push(Bytes::from_static(b"89"));
553 buf.truncate(20);
554 assert_eq!(buf.remaining(), 10);
555 buf.truncate(5);
556 assert_eq!(buf.remaining(), 5);
557 assert_eq!(buf.copy_to_bytes(5).as_ref(), b"01234");
558 assert_eq!(buf.remaining(), 0);
559 }
560
561 #[test]
562 fn chunked_buf_decodes_message_split_across_chunks() {
563 let payload: Vec<u8> = (0..20_000u32).map(|i| (i % 251) as u8).collect();
566 let msg = Bytes::from(payload);
567 let encoded = Bytes::from(msg.encode_to_vec());
568 for chunk_size in [1usize, 7, 64, 4096, usize::MAX] {
569 let mut buf = ChunkedBuf::default();
570 for start in (0..encoded.len()).step_by(chunk_size) {
571 buf.push(encoded.slice(start..min(start + chunk_size, encoded.len())));
572 }
573 buf.push(Bytes::from_static(b"footer"));
575 buf.truncate(encoded.len());
576 let decoded = Bytes::decode(buf).unwrap();
577 assert_eq!(decoded, msg, "chunk_size={chunk_size}");
578 }
579 }
580
581 #[tokio::test]
582 async fn read_message_decodes_body_larger_than_one_block_from_chunks() {
583 let store = ObjectStore::memory();
585 let path = Path::from("/large_message");
586 let payload = Bytes::from(
587 (0..300_000u32)
588 .map(|i| (i % 253) as u8)
589 .collect::<Vec<u8>>(),
590 );
591 let mut writer = store.create(&path).await.unwrap();
592 let pos = writer.write_protobuf(&payload).await.unwrap();
593 writer.write_magics(pos, 0, 1, b"LANC").await.unwrap();
594 writer.shutdown().await.unwrap();
595
596 let reader = store.open(&path).await.unwrap();
597 assert!(payload.len() > reader.block_size());
598 let decoded: Bytes = read_message(reader.as_ref(), pos).await.unwrap();
599 assert_eq!(decoded, payload);
600 }
601}