2022-09-17 15:45:41 +00:00
|
|
|
|
extern crate serde;
|
|
|
|
|
#[macro_use]
|
|
|
|
|
extern crate serde_derive;
|
|
|
|
|
// This allows inserting JSON documents
|
|
|
|
|
#[macro_use]
|
|
|
|
|
extern crate serde_json;
|
|
|
|
|
extern crate ledb;
|
|
|
|
|
// This allows define typed documents easy
|
|
|
|
|
#[macro_use]
|
|
|
|
|
extern crate ledb_derive;
|
|
|
|
|
extern crate ledb_types;
|
|
|
|
|
extern crate zip;
|
|
|
|
|
|
2022-09-17 18:08:13 +00:00
|
|
|
|
use clap::{Parser, Subcommand};
|
2022-09-17 15:45:41 +00:00
|
|
|
|
use rand::seq::IteratorRandom;
|
|
|
|
|
use std::time::Instant;
|
|
|
|
|
|
|
|
|
|
use std::{fs, io};
|
|
|
|
|
|
|
|
|
|
use ledb::{Options, Storage};
|
|
|
|
|
|
2022-09-17 18:08:13 +00:00
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
|
|
|
enum Command {
|
|
|
|
|
Write,
|
|
|
|
|
Compact,
|
|
|
|
|
Print {
|
|
|
|
|
#[clap(value_parser, default_value = "0")]
|
|
|
|
|
id: u32,
|
|
|
|
|
},
|
|
|
|
|
ZipPrint {
|
|
|
|
|
#[clap(value_parser, default_value = "0")]
|
|
|
|
|
file_num: usize,
|
|
|
|
|
#[clap(value_parser, default_value = "0")]
|
|
|
|
|
num: usize,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Parser, Debug)]
|
|
|
|
|
#[clap(author, version, about, long_about = None)]
|
|
|
|
|
#[clap(propagate_version = true)]
|
|
|
|
|
struct Cli {
|
|
|
|
|
#[clap(subcommand)]
|
|
|
|
|
command: Command,
|
|
|
|
|
#[clap(short, long, action)]
|
|
|
|
|
measure: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-17 15:45:41 +00:00
|
|
|
|
#[derive(Debug, Default, Clone, Deserialize)]
|
|
|
|
|
struct SourceQuestion {
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
num: u32,
|
|
|
|
|
id: String,
|
|
|
|
|
|
|
|
|
|
#[serde(alias = "Вопрос")]
|
|
|
|
|
description: String,
|
|
|
|
|
#[serde(alias = "Ответ")]
|
|
|
|
|
answer: String,
|
|
|
|
|
|
|
|
|
|
#[serde(alias = "Автор")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
author: String,
|
|
|
|
|
#[serde(alias = "Комментарий")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
comment: String,
|
|
|
|
|
#[serde(alias = "Комментарии")]
|
|
|
|
|
#[serde(alias = "Инфо")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
comment1: String,
|
|
|
|
|
#[serde(alias = "Тур")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
tour: String,
|
|
|
|
|
#[serde(alias = "Ссылка")]
|
|
|
|
|
#[serde(alias = "URL")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
url: String,
|
|
|
|
|
#[serde(alias = "Дата")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
date: String,
|
|
|
|
|
#[serde(alias = "Обработан")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
processed_by: String,
|
|
|
|
|
#[serde(alias = "Редактор")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
redacted_by: String,
|
|
|
|
|
#[serde(alias = "Копирайт")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
copyright: String,
|
|
|
|
|
#[serde(alias = "Тема")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
theme: String,
|
|
|
|
|
#[serde(alias = "Вид")]
|
|
|
|
|
#[serde(alias = "Тип")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
kind: String,
|
|
|
|
|
#[serde(alias = "Источник")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
source: String,
|
|
|
|
|
#[serde(alias = "Рейтинг")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
rating: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Clone, Deserialize)]
|
|
|
|
|
struct SourceQuestionsBatch {
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
filename: String,
|
|
|
|
|
#[serde(alias = "Пакет")]
|
|
|
|
|
#[serde(alias = "Чемпионат")]
|
|
|
|
|
description: String,
|
|
|
|
|
#[serde(alias = "Автор")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
author: String,
|
|
|
|
|
#[serde(alias = "Комментарий")]
|
|
|
|
|
#[serde(alias = "Комментарии")]
|
|
|
|
|
#[serde(alias = "Инфо")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
comment: String,
|
|
|
|
|
#[serde(alias = "Ссылка")]
|
|
|
|
|
#[serde(alias = "URL")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
url: String,
|
|
|
|
|
#[serde(alias = "Дата")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
date: String,
|
|
|
|
|
#[serde(alias = "Обработан")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
processed_by: String,
|
|
|
|
|
#[serde(alias = "Редактор")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
redacted_by: String,
|
|
|
|
|
#[serde(alias = "Копирайт")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
copyright: String,
|
|
|
|
|
#[serde(alias = "Тема")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
theme: String,
|
|
|
|
|
#[serde(alias = "Вид")]
|
|
|
|
|
#[serde(alias = "Тип")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
kind: String,
|
|
|
|
|
#[serde(alias = "Источник")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
source: String,
|
|
|
|
|
#[serde(alias = "Рейтинг")]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
rating: String,
|
|
|
|
|
#[serde(alias = "Вопросы")]
|
|
|
|
|
questions: Vec<SourceQuestion>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Clone, Serialize, Deserialize, Document)]
|
|
|
|
|
struct BatchInfo {
|
|
|
|
|
#[document(primary)]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
filename: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
description: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
author: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
comment: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
url: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
date: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
processed_by: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
redacted_by: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
copyright: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
theme: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
kind: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
source: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
rating: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Clone, Serialize, Deserialize, Document)]
|
|
|
|
|
struct Question {
|
|
|
|
|
#[document(primary)]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
num: u32,
|
|
|
|
|
#[document(index)]
|
|
|
|
|
id: String,
|
|
|
|
|
|
|
|
|
|
description: String,
|
|
|
|
|
answer: String,
|
|
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
author: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
comment: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
comment1: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
tour: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
url: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
date: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
processed_by: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
redacted_by: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
copyright: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
theme: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
kind: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
source: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
rating: String,
|
|
|
|
|
#[document(nested)]
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
batch_info: BatchInfo,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<SourceQuestion> for Question {
|
|
|
|
|
fn from(src: SourceQuestion) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
num: src.num,
|
|
|
|
|
id: src.id,
|
|
|
|
|
description: src.description,
|
|
|
|
|
answer: src.answer,
|
|
|
|
|
author: src.author,
|
|
|
|
|
comment: src.comment,
|
|
|
|
|
comment1: src.comment1,
|
|
|
|
|
tour: src.tour,
|
|
|
|
|
url: src.url,
|
|
|
|
|
date: src.date,
|
|
|
|
|
processed_by: src.processed_by,
|
|
|
|
|
redacted_by: src.redacted_by,
|
|
|
|
|
copyright: src.copyright,
|
|
|
|
|
theme: src.theme,
|
|
|
|
|
kind: src.kind,
|
|
|
|
|
source: src.source,
|
|
|
|
|
rating: src.rating,
|
|
|
|
|
batch_info: BatchInfo::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&SourceQuestionsBatch> for BatchInfo {
|
|
|
|
|
fn from(src: &SourceQuestionsBatch) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
filename: src.filename.clone(),
|
|
|
|
|
description: src.description.clone(),
|
|
|
|
|
author: src.author.clone(),
|
|
|
|
|
comment: src.comment.clone(),
|
|
|
|
|
url: src.url.clone(),
|
|
|
|
|
date: src.date.clone(),
|
|
|
|
|
processed_by: src.processed_by.clone(),
|
|
|
|
|
redacted_by: src.redacted_by.clone(),
|
|
|
|
|
copyright: src.copyright.clone(),
|
|
|
|
|
theme: src.theme.clone(),
|
|
|
|
|
kind: src.kind.clone(),
|
|
|
|
|
source: src.source.clone(),
|
|
|
|
|
rating: src.rating.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-17 16:13:17 +00:00
|
|
|
|
impl From<SourceQuestionsBatch> for Vec<Question> {
|
|
|
|
|
fn from(src: SourceQuestionsBatch) -> Self {
|
|
|
|
|
let mut result = Vec::<Question>::with_capacity(src.questions.len());
|
|
|
|
|
src.questions.iter().for_each(|item| {
|
2022-09-17 15:45:41 +00:00
|
|
|
|
let mut question: Question = item.clone().into();
|
2022-09-17 16:13:17 +00:00
|
|
|
|
question.batch_info = BatchInfo::from(&src);
|
2022-09-17 15:45:41 +00:00
|
|
|
|
result.push(question);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// measure and return time elapsed in `func` in seconds
|
|
|
|
|
pub fn measure<F: FnOnce()>(func: F) -> f64 {
|
|
|
|
|
let start = Instant::now();
|
|
|
|
|
func();
|
|
|
|
|
let elapsed = start.elapsed();
|
2022-09-17 16:13:17 +00:00
|
|
|
|
(elapsed.as_secs() as f64) + (elapsed.subsec_nanos() as f64 / 1_000_000_000.0)
|
2022-09-17 15:45:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn measure_and_print<F: FnOnce()>(func: F) {
|
|
|
|
|
let m = measure(func);
|
|
|
|
|
eprintln!("{}", m);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn writer_v4() {
|
|
|
|
|
let zip_file = fs::File::open("test1.zip").unwrap();
|
|
|
|
|
let zip_reader = io::BufReader::new(zip_file);
|
|
|
|
|
let mut archive = zip::ZipArchive::new(zip_reader).unwrap();
|
|
|
|
|
|
|
|
|
|
let options: Options = serde_json::from_value(json!({
|
|
|
|
|
"map_size": 900 * 1024 * 1024, // 900mb
|
|
|
|
|
"write_map": true,
|
|
|
|
|
"map_async": true,
|
|
|
|
|
"no_lock": true,
|
|
|
|
|
"no_meta_sync": true,
|
|
|
|
|
"no_sync": true,
|
|
|
|
|
}))
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let storage = Storage::new("db", options).unwrap();
|
|
|
|
|
let collection = storage.collection("questions").unwrap();
|
2022-09-17 18:08:13 +00:00
|
|
|
|
collection.purge().unwrap();
|
2022-09-17 15:45:41 +00:00
|
|
|
|
|
|
|
|
|
let mut count: usize = 0;
|
|
|
|
|
let count = &mut count;
|
|
|
|
|
(0..archive.len())
|
|
|
|
|
.map(|i| {
|
|
|
|
|
let file = archive.by_index(i).unwrap();
|
|
|
|
|
let name = file.mangled_name();
|
|
|
|
|
let name_str = name.to_str().unwrap();
|
|
|
|
|
|
|
|
|
|
let data: Result<SourceQuestionsBatch, _> = serde_json::from_reader(file);
|
|
|
|
|
|
|
|
|
|
(String::from(name_str), data)
|
|
|
|
|
})
|
|
|
|
|
.filter(|(_, data)| data.is_ok())
|
2022-09-17 16:13:17 +00:00
|
|
|
|
.flat_map(|(filename, data)| {
|
2022-09-17 15:45:41 +00:00
|
|
|
|
let mut data = data.unwrap();
|
2022-09-17 16:13:17 +00:00
|
|
|
|
data.filename = filename;
|
2022-09-17 15:45:41 +00:00
|
|
|
|
let questions: Vec<Question> = data.into();
|
|
|
|
|
questions
|
|
|
|
|
})
|
|
|
|
|
.for_each(|question| {
|
|
|
|
|
let result = collection.insert(&question);
|
|
|
|
|
if result.is_err() {
|
|
|
|
|
println!(
|
|
|
|
|
"Error: {:?} \n\
|
2022-09-17 16:13:17 +00:00
|
|
|
|
On: {:?}",
|
2022-09-17 15:45:41 +00:00
|
|
|
|
result, question
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
*count += 1;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
println!("inserted {}", count);
|
2022-09-17 16:13:17 +00:00
|
|
|
|
storage.sync(true).unwrap();
|
|
|
|
|
print!("stats: ");
|
|
|
|
|
let stats = storage.stat().unwrap();
|
|
|
|
|
println!("{:?}", stats);
|
|
|
|
|
drop(storage);
|
2022-09-17 15:45:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-09-17 18:08:13 +00:00
|
|
|
|
fn print_question_from<F>(get_q: F)
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce() -> Option<Question>,
|
|
|
|
|
{
|
|
|
|
|
let q = get_q().unwrap();
|
|
|
|
|
println!("{:#?}", q)
|
2022-09-17 15:45:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-09-17 18:08:13 +00:00
|
|
|
|
fn reader_v1(mut file_num: usize, mut num: usize) -> Option<Question> {
|
2022-09-17 15:45:41 +00:00
|
|
|
|
let zip_file = fs::File::open("test1.zip").unwrap();
|
|
|
|
|
let zip_reader = io::BufReader::new(zip_file);
|
|
|
|
|
let mut archive = zip::ZipArchive::new(zip_reader).unwrap();
|
|
|
|
|
|
|
|
|
|
let mut rng = rand::thread_rng();
|
2022-09-17 18:08:13 +00:00
|
|
|
|
if file_num == 0 {
|
|
|
|
|
file_num = (1..=archive.len()).choose(&mut rng).unwrap();
|
|
|
|
|
}
|
|
|
|
|
let file = archive.by_index(file_num - 1).unwrap();
|
2022-09-17 15:45:41 +00:00
|
|
|
|
let data: Result<SourceQuestionsBatch, _> = serde_json::from_reader(file);
|
|
|
|
|
let data = data.unwrap();
|
|
|
|
|
let questions: Vec<Question> = data.into();
|
|
|
|
|
|
2022-09-17 18:08:13 +00:00
|
|
|
|
if num == 0 {
|
|
|
|
|
num = (1..=questions.len()).choose(&mut rng).unwrap();
|
|
|
|
|
}
|
|
|
|
|
Some(questions[num - 1].clone())
|
2022-09-17 15:45:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn compact_db() {
|
|
|
|
|
let options: Options = serde_json::from_value(json!({
|
|
|
|
|
"write_map": true,
|
|
|
|
|
"map_async": true,
|
|
|
|
|
"no_lock": true,
|
|
|
|
|
"no_meta_sync": true,
|
|
|
|
|
"no_sync": true,
|
2022-09-17 18:08:13 +00:00
|
|
|
|
"compact": true,
|
2022-09-17 15:45:41 +00:00
|
|
|
|
}))
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let storage = Storage::new("db", options).unwrap();
|
|
|
|
|
|
2022-09-17 16:13:17 +00:00
|
|
|
|
storage.sync(true).unwrap();
|
|
|
|
|
let stats = storage.stat().unwrap();
|
|
|
|
|
println!("{:?}", stats);
|
|
|
|
|
drop(storage);
|
2022-09-17 15:45:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-09-17 18:08:13 +00:00
|
|
|
|
fn reader_v4(mut id: u32) -> Option<Question> {
|
2022-09-17 15:45:41 +00:00
|
|
|
|
let options: Options = serde_json::from_value(json!({
|
|
|
|
|
"read_only": true,
|
|
|
|
|
// "map_async": true,
|
|
|
|
|
"no_lock": true,
|
|
|
|
|
// "no_meta_sync": true,
|
|
|
|
|
// "no_sync": true,
|
|
|
|
|
// "no_tls": true
|
|
|
|
|
}))
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let storage = Storage::new("db", options).unwrap();
|
|
|
|
|
let collection = storage.collection("questions").unwrap();
|
|
|
|
|
let mut rng = rand::thread_rng();
|
2022-09-17 18:08:13 +00:00
|
|
|
|
|
|
|
|
|
if id == 0 {
|
|
|
|
|
let last_id = collection.last_id().unwrap();
|
|
|
|
|
id = (1..=last_id).choose(&mut rng).unwrap();
|
|
|
|
|
//let id = ledb::KeyData::Int(id as i64);
|
|
|
|
|
}
|
2022-09-17 15:45:41 +00:00
|
|
|
|
|
|
|
|
|
collection.get::<Question>(id).unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
2022-09-17 18:08:13 +00:00
|
|
|
|
let args = Cli::parse();
|
|
|
|
|
|
|
|
|
|
let mut action: Box<dyn FnOnce()> = match &args.command {
|
|
|
|
|
Command::Write => Box::new(writer_v4),
|
|
|
|
|
Command::Compact => Box::new(compact_db),
|
|
|
|
|
Command::Print { id } => {
|
|
|
|
|
let get_question = Box::new(|| reader_v4(*id));
|
|
|
|
|
Box::new(|| print_question_from(get_question))
|
|
|
|
|
}
|
|
|
|
|
Command::ZipPrint { file_num, num } => {
|
|
|
|
|
let get_question = Box::new(|| reader_v1(*file_num, *num));
|
|
|
|
|
Box::new(|| print_question_from(get_question))
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if args.measure {
|
|
|
|
|
action = Box::new(|| measure_and_print(action));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
action();
|
2022-09-17 15:45:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
use test::Bencher;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn reader_v1_works() {
|
|
|
|
|
reader_v1().unwrap();
|
|
|
|
|
}
|
|
|
|
|
fn reader_v4_works() {
|
|
|
|
|
reader_v4().unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[bench]
|
|
|
|
|
fn bench_reader_v1(b: &mut Bencher) {
|
|
|
|
|
b.iter(|| reader_v1());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[bench]
|
|
|
|
|
fn bench_reader_v4(b: &mut Bencher) {
|
|
|
|
|
b.iter(|| reader_v4());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*/
|