Skip to main content

dbase/
file.rs

1use crate::encoding::DynEncoding;
2use crate::field::{DeletionFlag, FieldsInfo, DELETION_FLAG_SIZE};
3use crate::header::Header;
4use crate::memo::MemoReader;
5use crate::reading::{ReadingOptions, BACKLINK_SIZE, TERMINATOR_VALUE};
6use crate::writing::{write_header_parts, WritableAsDbaseField};
7use crate::ErrorKind::UnsupportedCodePage;
8use crate::{
9    Encoding, Error, ErrorKind, FieldConversionError, FieldIOError, FieldInfo, FieldIterator,
10    FieldValue, FieldWriter, ReadableRecord, TableInfo, WritableRecord,
11};
12use byteorder::ReadBytesExt;
13use std::fmt::{Debug, Formatter};
14use std::io::{Cursor, Read, Seek, SeekFrom, Write};
15use std::path::Path;
16
17pub type BufReadWriteFile = bufrw::BufReaderWriter<std::fs::File>;
18
19/// Index to a field in a record
20#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
21pub struct FieldIndex(pub usize);
22
23/// Index to a record in a dBase file
24#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
25pub struct RecordIndex(pub usize);
26
27/// 'reference' to a field in a dBase file.
28///
29/// - Allows to read the field content via [Self::read] or [Self::read_as]
30/// - Allows to overwrite the field content via [Self::write]
31pub struct FieldRef<'a, T> {
32    file: &'a mut File<T>,
33    record_index: RecordIndex,
34    field_index: FieldIndex,
35}
36
37impl<T> Debug for FieldRef<'_, T> {
38    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
39        f.debug_struct("FieldRef")
40            .field("record_index", &self.record_index)
41            .field("field_index", &self.field_index)
42            .finish()
43    }
44}
45
46impl<T> FieldRef<'_, T> {
47    fn position_in_source(&self) -> u64 {
48        let record_position = self
49            .file
50            .header
51            .record_position(self.record_index.0)
52            .unwrap() as u64;
53
54        record_position + self.position_in_record() as u64
55    }
56
57    fn position_in_record(&self) -> usize {
58        self.file
59            .fields_info
60            .field_position_in_record(self.field_index.0)
61            .expect("internal error: invalid field index")
62    }
63}
64
65impl<T> FieldRef<'_, T>
66where
67    T: Seek,
68{
69    fn seek_to_beginning(&mut self) -> Result<u64, FieldIOError> {
70        let field_info = &self.file.fields_info[self.field_index.0];
71
72        self.file
73            .inner
74            .seek(SeekFrom::Start(self.position_in_source()))
75            .map_err(|e| FieldIOError::new(ErrorKind::IoError(e), Some(field_info.clone())))
76    }
77}
78
79impl<T> FieldRef<'_, T>
80where
81    T: Seek + Read,
82{
83    /// Reads and returns the value
84    pub fn read(&mut self) -> Result<FieldValue, Error> {
85        self.file
86            .ensure_record_has_been_read_into_buffer(self.record_index)?;
87
88        let field_info = &self.file.fields_info[self.field_index.0];
89
90        let start_pos = self.position_in_record();
91        let field_bytes = &mut self.file.record_data_buffer.get_mut()
92            [start_pos..start_pos + field_info.field_length as usize];
93
94        FieldValue::read_from(
95            field_bytes,
96            &mut self.file.memo_reader,
97            field_info,
98            &self.file.encoding,
99            self.file.options.character_trim,
100        )
101        .map_err(|e| {
102            Error::new(
103                FieldIOError::new(e, Some(field_info.clone())),
104                self.record_index.0,
105            )
106        })
107    }
108
109    /// Reads and returns the value converted to the requested type
110    pub fn read_as<ValueType>(&mut self) -> Result<ValueType, Error>
111    where
112        ValueType: TryFrom<FieldValue, Error = FieldConversionError>,
113    {
114        let value = self.read()?;
115
116        let converted_value = ValueType::try_from(value).map_err(|e| {
117            let field_info = &self.file.fields_info[self.field_index.0];
118            Error::new(
119                FieldIOError::new(ErrorKind::BadConversion(e), Some(field_info.clone())),
120                self.record_index.0,
121            )
122        })?;
123
124        Ok(converted_value)
125    }
126}
127
128impl<T> FieldRef<'_, T>
129where
130    T: Seek + Write,
131{
132    /// Writes the value
133    pub fn write<ValueType>(&mut self, value: &ValueType) -> Result<(), Error>
134    where
135        ValueType: WritableAsDbaseField,
136    {
137        self.file.file_position = self
138            .seek_to_beginning()
139            .map_err(|e| Error::new(e, self.record_index.0))?;
140
141        let field_info = &self.file.fields_info[self.field_index.0];
142
143        let start_pos = self.position_in_record();
144        let field_bytes = &mut self.file.record_data_buffer.get_mut()
145            [start_pos..start_pos + field_info.field_length as usize];
146        field_bytes.fill(0);
147
148        // Note that since we modify the internal buffer, we don't need to re-read the
149        // record / buffer, meaning if a user writes then reads it should get correct
150        // value, and we did not re-read from file.
151        let mut cursor = Cursor::new(field_bytes);
152        value
153            .write_as(field_info, &self.file.encoding, &mut cursor)
154            .map_err(|e| {
155                Error::new(
156                    FieldIOError::new(e, Some(field_info.clone())),
157                    self.record_index.0,
158                )
159            })?;
160
161        let buffer = cursor.into_inner();
162
163        self.file.inner.write_all(buffer).map_err(|e| {
164            Error::new(
165                FieldIOError::new(ErrorKind::IoError(e), Some(field_info.clone())),
166                self.record_index.0,
167            )
168        })?;
169
170        self.file.file_position += buffer.len() as u64;
171
172        Ok(())
173    }
174}
175
176/// 'reference' to a record in a dBase file.
177///
178/// This can be used to read/write the whole record at once,
179/// or select a particular field in the file [Self::field].
180///
181/// - Allows to read the field content via [Self::read] or [Self::read_as]
182/// - Allows to overwrite the field content via [Self::write]
183pub struct RecordRef<'a, T> {
184    file: &'a mut File<T>,
185    index: RecordIndex,
186}
187
188impl<T> Debug for RecordRef<'_, T> {
189    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
190        f.debug_struct("RecordRef")
191            .field("index", &self.index)
192            .finish()
193    }
194}
195
196impl<T> RecordRef<'_, T> {
197    pub fn field(&mut self, index: FieldIndex) -> Option<FieldRef<'_, T>> {
198        if index.0 >= self.file.fields_info.len() {
199            return None;
200        }
201        Some(FieldRef {
202            file: self.file,
203            record_index: self.index,
204            field_index: index,
205        })
206    }
207
208    fn position_in_source(&self) -> u64 {
209        self.file.header.record_position(self.index.0).unwrap() as u64
210    }
211}
212
213impl<T> RecordRef<'_, T>
214where
215    T: Seek,
216{
217    pub fn seek_before_deletion_flag(&mut self) -> Result<u64, FieldIOError> {
218        self.file
219            .inner
220            .seek(SeekFrom::Start(self.position_in_source()))
221            .map_err(|e| FieldIOError::new(ErrorKind::IoError(e), None))
222    }
223}
224
225impl<T> RecordRef<'_, T>
226where
227    T: Read + Seek,
228{
229    /// Returns the value of the special deletion flag
230    ///
231    /// - true -> the record is marked as deleted
232    /// - false -> the record is **not** marked as deleted
233    pub fn is_deleted(&mut self) -> Result<bool, Error> {
234        self.file
235            .ensure_record_has_been_read_into_buffer(self.index)?;
236        let deletion_flag = DeletionFlag::from_byte(self.file.record_data_buffer.get_ref()[0]);
237
238        Ok(deletion_flag == DeletionFlag::Deleted)
239    }
240
241    /// reads a field from the record
242    ///
243    /// Shortcut for `.field(index).unwrap().read().unwrap();`
244    pub fn read_field(&mut self, field_index: FieldIndex) -> Result<FieldValue, Error> {
245        let record_index = self.index.0;
246        let mut field = self
247            .field(field_index)
248            .ok_or_else(|| Error::new(FieldIOError::end_of_record(), record_index))?;
249        field.read()
250    }
251
252    /// reads a field from the record
253    ///
254    /// Shortcut for `.field(index).unwrap().read_as().unwrap();`
255    pub fn read_field_as<ValueType>(&mut self, field_index: FieldIndex) -> Result<ValueType, Error>
256    where
257        ValueType: TryFrom<FieldValue, Error = FieldConversionError>,
258    {
259        let record_index = self.index.0;
260        let mut field = self
261            .field(field_index)
262            .ok_or_else(|| Error::new(FieldIOError::end_of_record(), record_index))?;
263        field.read_as()
264    }
265
266    /// Reads the record
267    pub fn read(&mut self) -> Result<crate::Record, Error> {
268        self.read_as()
269    }
270
271    /// Reads the record as the given type
272    pub fn read_as<R>(&mut self) -> Result<R, Error>
273    where
274        R: ReadableRecord,
275    {
276        self.file
277            .ensure_record_has_been_read_into_buffer(self.index)?;
278        self.file
279            .record_data_buffer
280            .set_position(DELETION_FLAG_SIZE as u64);
281        let mut field_iterator = FieldIterator {
282            source: &mut self.file.record_data_buffer,
283            fields_info: self.file.fields_info.iter().peekable(),
284            memo_reader: &mut self.file.memo_reader,
285            field_data_buffer: &mut self.file.field_data_buffer,
286            encoding: &self.file.encoding,
287            options: self.file.options,
288        };
289
290        R::read_using(&mut field_iterator).map_err(|error| Error::new(error, self.index.0))
291    }
292}
293
294impl<T> RecordRef<'_, T>
295where
296    T: Write + Seek,
297{
298    /// writes a field to the record
299    ///
300    /// Shortcut for `.field(index).unwrap().write(&value).unwrap();`
301    pub fn write_field<ValueType>(
302        &mut self,
303        field_index: FieldIndex,
304        value: &ValueType,
305    ) -> Result<(), Error>
306    where
307        ValueType: WritableAsDbaseField,
308    {
309        let record_index = self.index.0;
310        let mut field = self
311            .field(field_index)
312            .ok_or_else(|| Error::new(FieldIOError::end_of_record(), record_index))?;
313        field.write(value)
314    }
315
316    /// Writes the content of `record` ath the position
317    /// pointed by `self`.
318    pub fn write<R>(&mut self, record: &R) -> Result<(), Error>
319    where
320        R: WritableRecord,
321    {
322        self.file.record_data_buffer.get_mut().fill(0);
323        self.file.record_data_buffer.get_mut()[0] = DeletionFlag::NotDeleted.to_byte();
324        self.file.record_data_buffer.set_position(1);
325
326        let mut field_writer = FieldWriter {
327            dst: &mut self.file.record_data_buffer,
328            fields_info: self.file.fields_info.iter().peekable(),
329            field_buffer: &mut Cursor::new(&mut self.file.field_data_buffer),
330            encoding: &self.file.encoding,
331        };
332
333        record
334            .write_using(&mut field_writer)
335            .map_err(|error| Error::new(error, self.index.0))?;
336
337        self.seek_before_deletion_flag()
338            .map_err(|error| Error::new(error, self.index.0))?;
339
340        self.file
341            .inner
342            .write_all(self.file.record_data_buffer.get_ref())
343            .map_err(|error| Error::io_error(error, self.index.0))?;
344
345        // We don't need to update the file's inner position as we re-wrote the whole record
346        debug_assert_eq!(
347            self.file.file_position,
348            self.file.inner.stream_position().unwrap()
349        );
350
351        Ok(())
352    }
353}
354
355/// Iterator over the records in a File
356pub struct FileRecordIterator<'a, T> {
357    file: &'a mut File<T>,
358    current_record: RecordIndex,
359}
360
361impl<T> FileRecordIterator<'_, T>
362where
363    T: Seek + Read,
364{
365    // To implement iterator we need the Iterator trait to make use of GATs
366    // which is not the case, to iteration will have to use the while let Some() pattern
367    pub fn next(&mut self) -> Option<RecordRef<'_, T>> {
368        let record_ref = self.file.record(self.current_record.0);
369        self.current_record.0 += usize::from(record_ref.is_some());
370        record_ref
371    }
372}
373
374/// Handle to a dBase File.
375///
376/// A `File`, allows to both read and write, it also
377/// allows to do modifications on an existing file,
378/// and enables to only read/modify parts of a file without
379/// first having to fully read it.
380///
381/// # Example
382///
383/// ```
384/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
385/// let mut file = dbase::File::open_read_only("tests/data/stations.dbf")?;
386///
387/// assert_eq!(file.num_records(), 86);
388///
389/// let name_idx = file.field_index("name").unwrap();
390/// let marker_color_idx = file.field_index("marker-col").unwrap();
391/// let marker_symbol_idx = file.field_index("marker-sym").unwrap();
392///
393/// // Test manually reading fields (not in correct order) to FieldValue
394/// let mut rh = file.record(3).unwrap();
395/// let marker_color = rh.field(marker_color_idx).unwrap().read()?;
396/// assert_eq!(
397///    marker_color,
398///    dbase::FieldValue::Character(Some("#ff0000".to_string()))
399/// );
400/// let name = rh.field(name_idx).unwrap().read()?;
401/// assert_eq!(
402///    name,
403///    dbase::FieldValue::Character(Some("Judiciary Sq".to_string()))
404/// );
405/// let marker_symbol = rh.field(marker_symbol_idx).unwrap().read()?;
406/// assert_eq!(
407///    marker_symbol,
408///    dbase::FieldValue::Character(Some("rail-metro".to_string()))
409/// );
410/// # Ok(())
411/// # }
412/// ```
413#[derive(Debug)]
414pub struct File<T> {
415    pub(crate) inner: T,
416    memo_reader: Option<MemoReader<T>>,
417    pub(crate) header: Header,
418    pub(crate) fields_info: FieldsInfo,
419    pub(crate) encoding: DynEncoding,
420    /// Buffer that contains a whole record worth of data
421    /// It also contains the deletion flag
422    record_data_buffer: Cursor<Vec<u8>>,
423    /// Non-Memo field length is stored on a u8,
424    /// so fields cannot exceed 255 bytes
425    field_data_buffer: [u8; 255],
426    pub(crate) options: ReadingOptions,
427    /// We track the position in the file
428    /// to avoid calling `seek` when we are reading buffer
429    /// in order (0, 1, 2, etc)
430    file_position: u64,
431}
432
433impl<T> File<T> {
434    /// Returns the information about fields present in the records
435    pub fn fields(&self) -> &[FieldInfo] {
436        self.fields_info.as_ref()
437    }
438
439    /// Returns the field index that corresponds to the given name
440    pub fn field_index(&self, name: &str) -> Option<FieldIndex> {
441        self.fields_info
442            .iter()
443            .position(|info| info.name.eq_ignore_ascii_case(name))
444            .map(FieldIndex)
445    }
446
447    /// Returns the number of records in the file
448    pub fn num_records(&self) -> usize {
449        self.header.num_records as usize
450    }
451
452    pub fn set_options(&mut self, options: ReadingOptions) {
453        self.options = options;
454    }
455}
456
457impl<T: Read + Seek> File<T> {
458    /// creates of File using source as the storage space with encoding.
459    pub fn open_with_encoding<E>(source: T, encoding: E) -> Result<Self, Error>
460    where
461        E: Encoding + 'static + Into<DynEncoding>,
462    {
463        Self::open_inner(source, Some(encoding))
464    }
465
466    /// creates of File using source as the storage space.
467    pub fn open(source: T) -> Result<Self, Error> {
468        Self::open_inner(source, None::<DynEncoding>)
469    }
470
471    pub(crate) fn open_inner<E>(mut source: T, encoding: Option<E>) -> Result<Self, Error>
472    where
473        E: Encoding + 'static + Into<DynEncoding>,
474    {
475        let mut header =
476            Header::read_from(&mut source).map_err(|error| Error::io_error(error, 0))?;
477
478        let offset = if header.file_type.is_visual_fox_pro() {
479            if BACKLINK_SIZE > header.offset_to_first_record {
480                panic!("Invalid file");
481            }
482            header.offset_to_first_record - BACKLINK_SIZE
483        } else {
484            header.offset_to_first_record
485        };
486        let num_fields = (offset as usize - Header::SIZE - size_of::<u8>()) / FieldInfo::SIZE;
487
488        // If encoding is specified, use it; otherwise, use the code page mark
489        let encoding = match encoding {
490            Some(encoding) => encoding.into(),
491            None => header.code_page_mark.to_encoding().ok_or_else(|| {
492                let field_error =
493                    FieldIOError::new(UnsupportedCodePage(header.code_page_mark), None);
494                Error::new(field_error, 0)
495            })?,
496        };
497
498        let fields_info =
499            FieldsInfo::read_from(&mut source, num_fields, &encoding).map_err(|error| Error {
500                record_num: 0,
501                field: None,
502                kind: error,
503            })?;
504
505        let terminator = source
506            .read_u8()
507            .map_err(|error| Error::io_error(error, 0))?;
508
509        debug_assert_eq!(terminator, TERMINATOR_VALUE);
510
511        source
512            .seek(SeekFrom::Start(u64::from(header.offset_to_first_record)))
513            .map_err(|error| Error::io_error(error, 0))?;
514
515        let record_size: usize = DELETION_FLAG_SIZE + fields_info.size_of_all_fields();
516        let record_data_buffer = Cursor::new(vec![0u8; record_size]);
517        // Some file seems not to include the DELETION_FLAG_SIZE into the record size,
518        // but we rely on it
519        header.size_of_record = record_size as u16;
520        // debug_assert_eq!(record_size - DELETION_FLAG_SIZE, header.size_of_record as usize);
521
522        Ok(Self {
523            inner: source,
524            memo_reader: None,
525            header,
526            fields_info,
527            encoding,
528            record_data_buffer,
529            field_data_buffer: [0u8; 255],
530            options: ReadingOptions::default(),
531            file_position: header.offset_to_first_record as u64,
532        })
533    }
534
535    /// Returns a reference to the record at the given index.
536    ///
537    /// Returns None if no record exist for the given index
538    pub fn record(&mut self, index: usize) -> Option<RecordRef<'_, T>> {
539        if index >= self.header.num_records as usize {
540            None
541        } else {
542            let record_ref = RecordRef {
543                file: self,
544                index: RecordIndex(index),
545            };
546            Some(record_ref)
547        }
548    }
549
550    /// Returns an iterator over the records in the file.
551    ///
552    /// Always starts at the first record
553    pub fn records(&mut self) -> FileRecordIterator<'_, T> {
554        FileRecordIterator {
555            file: self,
556            current_record: RecordIndex(0),
557        }
558    }
559
560    /// Returns true if it read from the source, false otherwise (used in tests).
561    fn ensure_record_has_been_read_into_buffer(
562        &mut self,
563        record_index: RecordIndex,
564    ) -> Result<bool, Error> {
565        let record_ref = RecordRef {
566            file: self,
567            index: record_index,
568        };
569        let start_of_record_pos = record_ref.position_in_source();
570        let end_of_record_pos = start_of_record_pos + u64::from(self.header.size_of_record);
571
572        if self.file_position > start_of_record_pos && self.file_position <= end_of_record_pos {
573            // If pos is in this range, then the record was already read into the buffer
574            return Ok(false);
575        }
576
577        if start_of_record_pos != self.file_position {
578            // Only call seek of the record we need to read
579            // is the just after the one we read last
580            self.file_position = self
581                .inner
582                .seek(SeekFrom::Start(start_of_record_pos))
583                .map_err(|e| Error::io_error(e, record_index.0))?;
584        }
585
586        self.inner
587            .read_exact(self.record_data_buffer.get_mut())
588            .map_err(|e| Error::io_error(e, record_index.0))?;
589        self.file_position += self.record_data_buffer.get_mut().len() as u64;
590        Ok(true)
591    }
592}
593
594impl<T: Write + Seek> File<T> {
595    pub fn create_new(mut dst: T, table_info: TableInfo) -> Result<Self, Error> {
596        write_header_parts(&mut dst, &table_info.header, &table_info.fields_info)?;
597        let record_size: usize = DELETION_FLAG_SIZE
598            + table_info
599                .fields_info
600                .iter()
601                .map(|i| i.field_length as usize)
602                .sum::<usize>();
603        let record_data_buffer = Cursor::new(vec![0u8; record_size]);
604        let file_position = table_info.header.offset_to_first_record as u64;
605        debug_assert_eq!(file_position, dst.stream_position().unwrap());
606        Ok(Self {
607            inner: dst,
608            memo_reader: None,
609            header: table_info.header,
610            fields_info: FieldsInfo {
611                inner: table_info.fields_info,
612            },
613            encoding: table_info.encoding,
614            record_data_buffer,
615            field_data_buffer: [0u8; 255],
616            options: ReadingOptions::default(),
617            file_position,
618        })
619    }
620
621    pub fn append_record<R>(&mut self, record: &R) -> Result<(), Error>
622    where
623        R: WritableRecord,
624    {
625        self.append_records(std::slice::from_ref(record))
626    }
627
628    pub fn append_records<R>(&mut self, records: &[R]) -> Result<(), Error>
629    where
630        R: WritableRecord,
631    {
632        assert!(
633            !self
634                .header
635                .num_records
636                .overflowing_add(records.len() as u32)
637                .1,
638            "Too many records (u32 overflow)"
639        );
640
641        let end_of_last_record = self.header.offset_to_first_record as u64
642            + (self.num_records() as u64 * self.header.size_of_record as u64);
643
644        self.inner
645            .seek(SeekFrom::Start(end_of_last_record))
646            .map_err(|error| Error::io_error(error, self.num_records()))?;
647
648        for record in records {
649            let current_record_index = self.header.num_records + 1;
650
651            let mut field_writer = FieldWriter {
652                dst: &mut self.inner,
653                fields_info: self.fields_info.iter().peekable(),
654                field_buffer: &mut Cursor::new(&mut self.field_data_buffer),
655                encoding: &self.encoding,
656            };
657
658            field_writer
659                .write_deletion_flag()
660                .map_err(|error| Error::io_error(error, current_record_index as usize))?;
661
662            record
663                .write_using(&mut field_writer)
664                .map_err(|error| Error::new(error, current_record_index as usize))?;
665
666            self.header.num_records = current_record_index;
667        }
668
669        self.sync_all()
670            .map_err(|error| Error::io_error(error, self.num_records()))?;
671
672        Ok(())
673    }
674
675    pub fn sync_all(&mut self) -> std::io::Result<()> {
676        let current_pos = self.inner.stream_position()?;
677        self.inner.seek(SeekFrom::Start(0))?;
678        self.header.write_to(&mut self.inner)?;
679        self.inner.seek(SeekFrom::Start(current_pos))?;
680        Ok(())
681    }
682}
683
684impl File<bufrw::BufReaderWriter<std::fs::File>> {
685    pub fn open_with_options<P: AsRef<Path>>(
686        path: P,
687        options: std::fs::OpenOptions,
688    ) -> Result<Self, Error> {
689        let file = options
690            .open(path)
691            .map_err(|error| Error::io_error(error, 0))?;
692        let source = bufrw::BufReaderWriter::new(file);
693        File::open(source)
694    }
695
696    /// Opens an existing dBase file in read only mode
697    pub fn open_read_only<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
698        let file = std::fs::File::open(path.as_ref()).map_err(|error| Error::io_error(error, 0))?;
699
700        let mut file = File::open(bufrw::BufReaderWriter::new(file))?;
701        if file.fields_info.at_least_one_field_is_memo() {
702            let p = path.as_ref();
703            let memo_type = file.header.file_type.supported_memo_type();
704            if let Some(mt) = memo_type {
705                let memo_path = p.with_extension(mt.extension());
706
707                let memo_file = std::fs::File::open(memo_path).map_err(|error| Error {
708                    record_num: 0,
709                    field: None,
710                    kind: ErrorKind::ErrorOpeningMemoFile(error),
711                })?;
712
713                let memo_reader =
714                    MemoReader::new(mt, bufrw::BufReaderWriter::new(memo_file)).unwrap();
715
716                file.memo_reader = Some(memo_reader);
717            }
718        }
719        Ok(file)
720    }
721
722    /// Opens an existing dBase file in write only mode
723    pub fn open_write_only<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
724        let mut options = std::fs::OpenOptions::new();
725        options
726            .read(false)
727            .write(true)
728            .create(false)
729            .truncate(false);
730
731        File::open_with_options(path, options)
732    }
733
734    /// Opens an existing dBase file in read **and** write mode
735    pub fn open_read_write<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
736        let mut options = std::fs::OpenOptions::new();
737        options.read(true).write(true).create(false).truncate(false);
738
739        File::open_with_options(path, options)
740    }
741
742    /// This function will create a file if it does not exist, and will truncate it if it does.
743    pub fn create<P: AsRef<Path>>(path: P, table_info: TableInfo) -> Result<Self, Error> {
744        let file = std::fs::File::create(path).map_err(|error| Error::io_error(error, 0))?;
745
746        File::create_new(bufrw::BufReaderWriter::new(file), table_info)
747    }
748}
749
750#[cfg(test)]
751mod tests {
752    #[test]
753    fn ensure_record_has_been_read_into_buffer() {
754        let mut file = crate::File::open_read_only("tests/data/stations.dbf").unwrap();
755
756        {
757            let mut record = file.record(0).unwrap();
758            let _ = record.read_field(crate::FieldIndex(0)).unwrap();
759            // Must return false, meaning it correctly understands the record 0 is in memory
760            assert!(!file
761                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(0))
762                .unwrap());
763            assert!(file
764                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(1))
765                .unwrap());
766        }
767
768        {
769            let mut record = file.record(4).unwrap();
770            let _ = record.read_field(crate::FieldIndex(3)).unwrap();
771            // Must return false, meaning it correctly understands the record 4 is in memory
772            assert!(!file
773                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(4))
774                .unwrap());
775            assert!(file
776                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(1))
777                .unwrap());
778        }
779
780        // Make sure writing a field still work with the ensure mechanism
781        {
782            let mut record = file.record(10).unwrap();
783            let value = record.read_field(crate::FieldIndex(2)).unwrap();
784            // Use record.file to avoid double borrow
785            assert!(!record
786                .file
787                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(10))
788                .unwrap());
789            record.write_field(crate::FieldIndex(2), &value).unwrap();
790            assert!(!file
791                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(10))
792                .unwrap());
793        }
794    }
795}