Server: Adds "dump" command

pull/410/head
Tpt 2 years ago committed by Thomas Tanon
parent 84d6d48b0e
commit d42e2a818c
  1. 228
      server/src/main.rs

@ -19,7 +19,7 @@ use std::cmp::{max, min};
use std::ffi::OsStr;
use std::fmt;
use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Read, Write};
use std::io::{self, stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::str;
@ -116,6 +116,28 @@ enum Command {
#[arg(long)]
graph: Option<String>,
},
/// Dump the store content into a file.
Dump {
/// Directory in which the data stored by Oxigraph are persisted.
#[arg(short, long)]
location: PathBuf,
/// file to dump to.
///
/// If no file is given, stdout is used.
file: Option<PathBuf>,
/// The format of the file(s) to dump.
///
/// Can be an extension like "nt" or a MIME type like "application/n-triples".
///
/// By default the format is guessed from the target file extension.
#[arg(long, required_unless_present = "file")]
format: Option<String>,
/// Name of the graph to dump.
///
/// By default all graphs are dumped if the output format supports datasets.
#[arg(long)]
graph: Option<String>,
},
}
pub fn main() -> anyhow::Result<()> {
@ -273,6 +295,44 @@ pub fn main() -> anyhow::Result<()> {
Ok(())
}
}
Command::Dump {
location,
file,
format,
graph,
} => {
let store = Store::open_read_only(&location)?;
let format = if let Some(format) = format {
GraphOrDatasetFormat::from_str(&format)?
} else if let Some(file) = &file {
GraphOrDatasetFormat::from_path(file)?
} else {
bail!("The --format option must be set when writing to stdout")
};
let graph = if let Some(graph) = &graph {
Some(if graph.eq_ignore_ascii_case("default") {
GraphName::DefaultGraph
} else {
NamedNodeRef::new(graph)
.with_context(|| format!("The target graph name {graph} is invalid"))?
.into()
})
} else {
None
};
if let Some(file) = file {
dump(
&store,
BufWriter::new(File::create(&file).map_err(|e| {
anyhow!("Error while opening file {}: {e}", file.display())
})?),
format,
graph,
)
} else {
dump(&store, stdout().lock(), format, graph)
}
}
}
}
@ -300,6 +360,28 @@ fn bulk_load(
Ok(())
}
fn dump(
store: &Store,
writer: impl Write,
format: GraphOrDatasetFormat,
to_graph_name: Option<GraphName>,
) -> anyhow::Result<()> {
match format {
GraphOrDatasetFormat::Graph(format) => store.dump_graph(
writer,
format,
&to_graph_name.ok_or_else(|| anyhow!("The --graph option is required when writing a graph format like NTriples, Turtle or RDF/XML"))?,
)?,
GraphOrDatasetFormat::Dataset(format) => {
if to_graph_name.is_some() {
bail!("The --graph option is not allowed when writing a dataset format like NQuads or TriG");
}
store.dump_dataset(writer, format)?
}
}
Ok(())
}
#[derive(Copy, Clone)]
enum GraphOrDatasetFormat {
Graph(GraphFormat),
@ -1239,7 +1321,7 @@ mod tests {
use super::*;
use anyhow::Result;
use assert_cmd::Command;
use assert_fs::prelude::*;
use assert_fs::{prelude::*, NamedTempFile, TempDir};
use flate2::write::GzEncoder;
use flate2::Compression;
use oxhttp::model::Method;
@ -1266,38 +1348,66 @@ mod tests {
}
#[test]
fn cli_load_graph() -> Result<()> {
let file = assert_fs::NamedTempFile::new("sample.nt")?;
file.write_str("<http://example.com/s> <http://example.com/p> <http://example.com/o> .")?;
fn cli_load_and_dump_graph() -> Result<()> {
let store_dir = TempDir::new()?;
let input_file = NamedTempFile::new("input.nt")?;
input_file
.write_str("<http://example.com/s> <http://example.com/p> <http://example.com/o> .")?;
cli_command()?
.arg("load")
.arg("--location")
.arg(store_dir.path())
.arg("-f")
.arg(file.path())
.arg(input_file.path())
.assert()
.success()
.stdout("")
.stderr(predicate::str::contains("1 triples loaded"));
.success();
let output_file = NamedTempFile::new("output.nt")?;
cli_command()?
.arg("dump")
.arg("--location")
.arg(store_dir.path())
.arg(output_file.path())
.arg("--graph")
.arg("default")
.assert()
.success();
output_file
.assert("<http://example.com/s> <http://example.com/p> <http://example.com/o> .\n");
Ok(())
}
#[test]
fn cli_load_dataset() -> Result<()> {
let file = assert_fs::NamedTempFile::new("sample.nq")?;
file.write_str("<http://example.com/s> <http://example.com/p> <http://example.com/o> .")?;
fn cli_load_and_dump_dataset() -> Result<()> {
let store_dir = TempDir::new()?;
let input_file = NamedTempFile::new("input.nq")?;
input_file
.write_str("<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .")?;
cli_command()?
.arg("load")
.arg("--location")
.arg(store_dir.path())
.arg("-f")
.arg(file.path())
.arg(input_file.path())
.assert()
.success()
.stdout("")
.stderr(predicate::str::contains("1 triples loaded"));
.success();
let output_file = NamedTempFile::new("output.nq")?;
cli_command()?
.arg("dump")
.arg("--location")
.arg(store_dir.path())
.arg(output_file.path())
.assert()
.success();
output_file
.assert("<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
Ok(())
}
#[test]
fn cli_load_gzip_dataset() -> Result<()> {
let file = assert_fs::NamedTempFile::new("sample.nq.gz")?;
let file = NamedTempFile::new("sample.nq.gz")?;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder
.write_all(b"<http://example.com/s> <http://example.com/p> <http://example.com/o> .")?;
@ -1307,42 +1417,98 @@ mod tests {
.arg("-f")
.arg(file.path())
.assert()
.success()
.stdout("")
.stderr(predicate::str::contains("1 triples loaded"));
.success();
Ok(())
}
#[test]
fn cli_load_with_graph_and_format() -> Result<()> {
let file = assert_fs::NamedTempFile::new("sample")?;
file.write_str("<http://example.com/s> <http://example.com/p> <http://example.com/o> .")?;
fn cli_load_and_dump_named_graph() -> Result<()> {
let store_dir = TempDir::new()?;
let input_file = NamedTempFile::new("input.nt")?;
input_file
.write_str("<http://example.com/s> <http://example.com/p> <http://example.com/o> .")?;
cli_command()?
.arg("load")
.arg("--location")
.arg(store_dir.path())
.arg("-f")
.arg(file.path())
.arg(input_file.path())
.arg("--graph")
.arg("http://example.com/g")
.assert()
.success();
let output_file = NamedTempFile::new("output.nt")?;
cli_command()?
.arg("dump")
.arg("--location")
.arg(store_dir.path())
.arg(output_file.path())
.arg("--graph")
.arg("http://example.com/g")
.assert()
.success();
output_file
.assert("<http://example.com/s> <http://example.com/p> <http://example.com/o> .\n");
Ok(())
}
#[test]
fn cli_load_and_dump_with_format() -> Result<()> {
let store_dir = TempDir::new()?;
let input_file = NamedTempFile::new("input")?;
input_file
.write_str("<http://example.com/s> <http://example.com/p> <http://example.com/o> .")?;
cli_command()?
.arg("load")
.arg("--location")
.arg(store_dir.path())
.arg("-f")
.arg(input_file.path())
.arg("--format")
.arg("nt")
.assert()
.success();
let output_file = NamedTempFile::new("output")?;
cli_command()?
.arg("dump")
.arg("--location")
.arg(store_dir.path())
.arg(output_file.path())
.arg("--graph")
.arg("http://example.com")
.arg("default")
.arg("--format")
.arg("nt")
.assert()
.success()
.stdout("")
.stderr(predicate::str::contains("1 triples loaded"));
.success();
output_file
.assert("<http://example.com/s> <http://example.com/p> <http://example.com/o> .\n");
Ok(())
}
#[test]
fn cli_load_from_stdin() -> Result<()> {
fn cli_load_from_stdin_and_dump_to_stdout() -> Result<()> {
let store_dir = TempDir::new()?;
cli_command()?
.arg("load")
.arg("--location")
.arg(store_dir.path())
.arg("--format")
.arg("nq")
.write_stdin("<http://example.com/s> <http://example.com/p> <http://example.com/o> .")
.assert()
.success();
cli_command()?
.arg("dump")
.arg("--location")
.arg(store_dir.path())
.arg("--format")
.arg("nq")
.assert()
.success()
.stdout("")
.stderr(predicate::str::contains("1 triples loaded"));
.stdout("<http://example.com/s> <http://example.com/p> <http://example.com/o> .\n");
Ok(())
}

Loading…
Cancel
Save