1
use std::io::{self, Read};
2

            
3
use okaywal::{Entry, EntryId, LogManager, SegmentReader, WriteAheadLog};
4

            
5
1
fn main() -> io::Result<()> {
6
    // begin rustme snippet: readme-example
7
    // Open a log using a Checkpointer that echoes the information passed into each
8
    // function that the Checkpointer trait defines.
9
1
    let log = WriteAheadLog::recover("my-log", LoggingCheckpointer)?;
10

            
11
    // Begin writing an entry to the log.
12
1
    let mut writer = log.begin_entry()?;
13

            
14
    // Each entry is one or more chunks of data. Each chunk can be individually
15
    // addressed using its LogPosition.
16
1
    let record = writer.write_chunk("this is the first entry".as_bytes())?;
17

            
18
    // To fully flush all written bytes to disk and make the new entry
19
    // resilient to a crash, the writer must be committed.
20
1
    writer.commit()?;
21
    // end rustme snippet
22

            
23
    // Let's reopen the log. During this process,
24
    // LoggingCheckpointer::should_recover_segment will be invoked for each segment
25
    // file that has not been checkpointed yet. In this example, it will be called
26
    // once. Once the Checkpointer confirms the data should be recovered,
27
    // LoggingCheckpointer::recover will be invoked once for each entry in the WAL
28
    // that hasn't been previously checkpointed.
29
1
    drop(log);
30
1
    let log = WriteAheadLog::recover("my-log", LoggingCheckpointer)?;
31

            
32
    // We can use the previously returned DataRecord to read the original data.
33
1
    let mut reader = log.read_at(record.position)?;
34
1
    let mut buffer = vec![0; usize::try_from(record.length).unwrap()];
35
1
    reader.read_exact(&mut buffer)?;
36
1
    println!(
37
1
        "Data read from log: {}",
38
1
        String::from_utf8(buffer).expect("invalid utf-8")
39
1
    );
40
1

            
41
1
    // Cleanup
42
1
    drop(reader);
43
1
    drop(log);
44
1
    std::fs::remove_dir_all("my-log")?;
45

            
46
1
    Ok(())
47
1
}
48

            
49
#[derive(Debug)]
50
struct LoggingCheckpointer;
51

            
52
impl LogManager for LoggingCheckpointer {
53
1
    fn recover(&mut self, entry: &mut Entry<'_>) -> io::Result<()> {
54
        // This example uses read_all_chunks to load the entire entry into
55
        // memory for simplicity. The crate also supports reading each chunk
56
        // individually to minimize memory usage.
57
1
        if let Some(all_chunks) = entry.read_all_chunks()? {
58
1
            // Convert the Vec<u8>'s to Strings.
59
1
            let all_chunks = all_chunks
60
1
                .into_iter()
61
1
                .map(String::from_utf8)
62
1
                .collect::<Result<Vec<String>, _>>()
63
1
                .expect("invalid utf-8");
64
1
            println!(
65
1
                "LoggingCheckpointer::recover(entry_id: {:?}, data: {:?})",
66
1
                entry.id(),
67
1
                all_chunks,
68
1
            );
69
1
        } else {
70
            // This entry wasn't completely written. This could happen if a
71
            // power outage or crash occurs while writing an entry.
72
        }
73

            
74
1
        Ok(())
75
1
    }
76

            
77
    fn checkpoint_to(
78
        &mut self,
79
        last_checkpointed_id: EntryId,
80
        _checkpointed_entries: &mut SegmentReader,
81
        _wal: &WriteAheadLog,
82
    ) -> io::Result<()> {
83
        // checkpoint_to is called once enough data has been written to the
84
        // WriteAheadLog. After this function returns, the log will recycle the
85
        // file containing the entries being checkpointed.
86
        //
87
        // This function is where the entries must be persisted to the storage
88
        // layer the WriteAheadLog is sitting in front of. To ensure ACID
89
        // compliance of the combination of the WAL and the storage layer, the
90
        // storage layer must be fully resilliant to losing any changes made by
91
        // the checkpointed entries before this function returns.
92
        println!("LoggingCheckpointer::checkpoint_to({last_checkpointed_id:?}");
93
        Ok(())
94
    }
95
}
96

            
97
1
#[test]
98
1
fn test() -> io::Result<()> {
99
1
    // Clean up any previous runs of this example.
100
1
    let path = std::path::Path::new("my-log");
101
1
    if path.exists() {
102
        std::fs::remove_dir_all("my-log")?;
103
1
    }
104

            
105
1
    main()
106
1
}