Skip to main content

kermit_bench/benchmarks/
oxford.rs

1use {
2    crate::{
3        benchmark::{BenchmarkConfig, BenchmarkMetadata, SubTask, Task},
4        downloader::{DownloadMethod, DownloadSpec},
5        utils,
6    },
7    std::path::Path,
8};
9
10/// The Oxford Database Systems and Implementation course benchmark.
11///
12/// Downloads relation data from the leapfrog-triejoin reference repository,
13/// converts it to Parquet format, and organizes it by distribution (Uniform,
14/// Zipf) and scale factor.
15pub struct OxfordBenchmark;
16
17static METADATA: BenchmarkMetadata = BenchmarkMetadata {
18    name: "Oxford Dataset",
19    description: "Oxford Database Systems and Implementation final course exam",
20    download_spec: DownloadSpec {
21        name: "oxford_dataset",
22        method: DownloadMethod::CLONE,
23        url: "https://github.com/schroederdewitt/leapfrog-triejoin",
24    },
25    tasks: &[
26        Task {
27            name: "Uniform",
28            description: "Uniformly distributed data",
29            subtasks: &[
30                SubTask {
31                    name: "Scale 1",
32                    data_paths: &["data/dataset1-uniform/scale1"],
33                    query_paths: &[
34                        "queries/query1.txt",
35                        "queries/query2.txt",
36                        "queries/query3.txt",
37                    ],
38                    description: "Scale 1 Dataset",
39                },
40                SubTask {
41                    name: "Scale 2",
42                    data_paths: &["data/dataset1-uniform/scale2"],
43                    query_paths: &[
44                        "queries/query1.txt",
45                        "queries/query2.txt",
46                        "queries/query3.txt",
47                    ],
48                    description: "Scale 2 Dataset",
49                },
50                SubTask {
51                    name: "Scale 3",
52                    data_paths: &["data/dataset1-uniform/scale3"],
53                    query_paths: &[
54                        "queries/query1.txt",
55                        "queries/query2.txt",
56                        "queries/query3.txt",
57                    ],
58                    description: "Scale 3 Dataset",
59                },
60                SubTask {
61                    name: "Scale 4",
62                    data_paths: &["data/dataset1-uniform/scale4"],
63                    query_paths: &[
64                        "queries/query1.txt",
65                        "queries/query2.txt",
66                        "queries/query3.txt",
67                    ],
68                    description: "Scale 4 Dataset",
69                },
70                SubTask {
71                    name: "Scale 5",
72                    data_paths: &["data/dataset1-uniform/scale5"],
73                    query_paths: &[
74                        "queries/query1.txt",
75                        "queries/query2.txt",
76                        "queries/query3.txt",
77                    ],
78                    description: "Scale 5 Dataset",
79                },
80                SubTask {
81                    name: "Scale 6",
82                    data_paths: &["data/dataset1-uniform/scale6"],
83                    query_paths: &[
84                        "queries/query1.txt",
85                        "queries/query2.txt",
86                        "queries/query3.txt",
87                    ],
88                    description: "Scale 6 Dataset",
89                },
90            ],
91        },
92        Task {
93            name: "Zipf",
94            description: "Zipf distributed data",
95            subtasks: &[
96                SubTask {
97                    name: "Scale 1",
98                    data_paths: &["data/dataset2-zipf/scale1"],
99                    query_paths: &[
100                        "queries/query1.txt",
101                        "queries/query2.txt",
102                        "queries/query3.txt",
103                    ],
104                    description: "Scale 1 Dataset",
105                },
106                SubTask {
107                    name: "Scale 2",
108                    data_paths: &["data/dataset2-zipf/scale2"],
109                    query_paths: &[
110                        "queries/query1.txt",
111                        "queries/query2.txt",
112                        "queries/query3.txt",
113                    ],
114                    description: "Scale 2 Dataset",
115                },
116                SubTask {
117                    name: "Scale 3",
118                    data_paths: &["data/dataset2-zipf/scale3"],
119                    query_paths: &[
120                        "queries/query1.txt",
121                        "queries/query2.txt",
122                        "queries/query3.txt",
123                    ],
124                    description: "Scale 3 Dataset",
125                },
126                SubTask {
127                    name: "Scale 4",
128                    data_paths: &["data/dataset2-zipf/scale4"],
129                    query_paths: &[
130                        "queries/query1.txt",
131                        "queries/query2.txt",
132                        "queries/query3.txt",
133                    ],
134                    description: "Scale 4 Dataset",
135                },
136                SubTask {
137                    name: "Scale 5",
138                    data_paths: &["data/dataset2-zipf/scale5"],
139                    query_paths: &[
140                        "queries/query1.txt",
141                        "queries/query2.txt",
142                        "queries/query3.txt",
143                    ],
144                    description: "Scale 5 Dataset",
145                },
146                SubTask {
147                    name: "Scale 6",
148                    data_paths: &["data/dataset2-zipf/scale6"],
149                    query_paths: &[
150                        "queries/query1.txt",
151                        "queries/query2.txt",
152                        "queries/query3.txt",
153                    ],
154                    description: "Scale 6 Dataset",
155                },
156            ],
157        },
158    ],
159};
160
161fn translate_dataset(source: &Path, dest: &Path) -> Result<(), Box<dyn std::error::Error>> {
162    let header_path = source.join("databasefile");
163    let headers: Vec<String> = std::fs::read_to_string(&header_path)
164        .map_err(|e| format!("Failed to read header file {}: {e}", header_path.display()))?
165        .lines()
166        .map(|s| s.to_string())
167        .collect();
168
169    for header in headers {
170        let header_content: Vec<String> = header.split(",").map(|s| s.trim().to_string()).collect();
171
172        let rel_file_name = &header_content[0];
173        let rel_name = &header_content[1];
174        let rel_attrs: Vec<String> = header_content[2..]
175            .iter()
176            .map(|s| s.trim().to_string())
177            .filter(|s| !s.is_empty())
178            .collect();
179
180        let data_path = source.join(rel_file_name);
181
182        let raw = std::fs::read_to_string(&data_path)
183            .map_err(|e| format!("Failed to read data file {}: {e}", data_path.display()))?;
184        let data_content: Vec<Vec<usize>> = raw
185            .trim()
186            .lines()
187            .map(|line| {
188                line.split(",")
189                    .take(rel_attrs.len())
190                    .map(|s| {
191                        s.trim().parse::<usize>().map_err(|e| {
192                            format!(
193                                "Failed to parse value {:?} in {}: {e}",
194                                s.trim(),
195                                data_path.display()
196                            )
197                        })
198                    })
199                    .collect::<Result<Vec<_>, _>>()
200            })
201            .collect::<Result<Vec<_>, _>>()?;
202
203        let parquet_path = dest.join(format!("{}.parquet", rel_name));
204        utils::write_relation_to_parquet(&parquet_path, &rel_attrs, &data_content)
205            .map_err(|e| format!("Failed to write {}: {e}", parquet_path.display()))?;
206    }
207    Ok(())
208}
209
210fn translate_query(source: &Path, dest: &Path) -> Result<(), Box<dyn std::error::Error>> {
211    let query_content: Vec<_> = std::fs::read_to_string(source)
212        .map_err(|e| format!("Failed to read query file {}: {e}", source.display()))?
213        .lines()
214        .map(|s| s.to_string())
215        .collect();
216    let rels = query_content[0]
217        .split(",")
218        .filter(|s| !s.is_empty())
219        .map(|s| s.trim())
220        .collect::<Vec<_>>();
221    let attrs = query_content[1]
222        .split(",")
223        .filter(|s| !s.is_empty())
224        .map(|s| s.trim())
225        .collect::<Vec<_>>();
226    let mut file = std::fs::File::create(dest)
227        .map_err(|e| format!("Failed to create query output {}: {e}", dest.display()))?;
228    use std::io::Write;
229    writeln!(file, "{}", rels.join(","))?;
230    writeln!(file, "{}", attrs.join(","))?;
231    Ok(())
232}
233
234impl BenchmarkConfig for OxfordBenchmark {
235    fn metadata(&self) -> &BenchmarkMetadata { &METADATA }
236
237    fn load(&self, source: &Path, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
238        let dl_spec = &self.metadata().download_spec;
239
240        //////////////////////
241        // DATA PROCESSING  //
242        //////////////////////
243
244        // oxford_dataset/data
245        let data_dir = path.join(dl_spec.name).join("data");
246
247        // leapfrog-triejoin/datasets
248        let ds_tmp_path = source.join("datasets");
249
250        for dataset_parent in ["dataset1-uniform", "dataset2-zipf"] {
251            for dataset_sub in ["scale1", "scale2", "scale3", "scale4", "scale5", "scale6"] {
252                // leapfrog-triejoin/datasets/dataset1-uniform/scale1
253                let source_path = ds_tmp_path.join(dataset_parent).join(dataset_sub);
254
255                // oxford_dataset/data/dataset1-uniform/scale1
256                let dest_path = data_dir.join(dataset_parent).join(dataset_sub);
257
258                // if destination path does not exist, create it
259                if !dest_path.exists() {
260                    std::fs::create_dir_all(&dest_path)?;
261                }
262
263                translate_dataset(&source_path, &dest_path)?;
264            }
265        }
266
267        // oxford_dataset/queries
268        let queries_dest = path.join(dl_spec.name).join("queries");
269
270        if !queries_dest.exists() {
271            std::fs::create_dir_all(&queries_dest)?;
272        }
273        for query_file in ["query1", "query2", "query3"] {
274            // leapfrog-triejoin/datasets/query1
275            let source_path = ds_tmp_path.join(query_file);
276            // oxford_dataset/queries/query1.txt
277            let dest_path = queries_dest.join(format!("{}.txt", query_file));
278            translate_query(&source_path, &dest_path)?;
279        }
280
281        Ok(())
282    }
283}