From f9ef0c750df75305d52baf507e551c1644105277 Mon Sep 17 00:00:00 2001 From: kennytm Date: Sun, 22 Dec 2019 22:52:42 +0800 Subject: [PATCH] Replace failure by thiserror + anyhow. Update dependencies. --- .travis.yml | 8 +- Cargo.toml | 38 ++-- src/bin/dbgen.rs | 2 +- src/cli.rs | 131 +++++--------- src/error.rs | 121 ++++--------- src/eval.rs | 88 ++++++---- src/parser.rs | 18 +- src/schemagen_cli.rs | 69 +++----- src/value.rs | 32 ++-- tests/check.rs | 2 +- tests/data/rand-finite-float/result.1.sql | 200 +++++++++++----------- tests/data/seeded-hc128/result.1.sql | 196 ++++++++++----------- 12 files changed, 389 insertions(+), 516 deletions(-) diff --git a/.travis.yml b/.travis.yml index 10da380..d4292c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: rust jobs: include: - - rust: 1.31.0 + - rust: stable os: linux - - rust: 1.31.0 + - rust: stable os: osx - # - rust: 1.31.0 - # os: windows + # - rust: stable + # os: windows # windows build currently causes GCC on Travis to ICE. - rust: beta os: linux - rust: nightly diff --git a/Cargo.toml b/Cargo.toml index 4ecec6e..9b23f5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dbgen" -version = "0.5.0" +version = "0.6.0" authors = ["kennytm "] edition = "2018" license = "MIT" @@ -18,37 +18,39 @@ is-it-maintained-open-issues = { repository = "kennytm/dbgen" } maintenance = { status = "actively-developed" } [dependencies] -structopt = "0.2" -pest = "2.0" -pest_derive = "2.0" -failure = "0.1" -rand = "0.6" +structopt = "0.3" +pest = "2.1" +pest_derive = "2.1" +anyhow = "1.0" +thiserror = "1.0" +rand = "0.7" data-encoding = "2.1" regex-syntax = "0.6" pbr = "1.0" num-traits = "0.2" -rayon = "1.0" -zipf = "5.0" +rayon = "1.3" +zipf = "6.0" chrono = { version = "0.4", default-features = false } chrono-tz = { version = "0.5.1", features = ["serde"] } -ryu = "0.2" +ryu = "1.0" serde_derive = "1.0" serde = "1.0" muldiv = "0.2" -rand_regex = "0.12" -rand_pcg = "0.1" -rand_isaac = "0.1" -rand_chacha = "0.1" -rand_hc = "0.1" -rand_xorshift = "0.1" +rand_distr = "0.2" +rand_regex = "0.13" +rand_pcg = "0.2" +rand_isaac = "0.2" +rand_chacha = "0.2" +rand_hc = "0.2" +rand_xorshift = "0.2" shlex = "0.1" flate2 = "1.0" xz2 = "0.1" -zstd = { version = "0.4", default-features = false } +zstd = { version = "0.5", default-features = false } [dev-dependencies] -regex = "1.1" -tempfile = "3.0" +regex = { version = "1.3", default-features = false } +tempfile = "3.1" serde_json = "1.0" diff = "0.1" diff --git a/src/bin/dbgen.rs b/src/bin/dbgen.rs index d9c4bdc..c9c3d87 100644 --- a/src/bin/dbgen.rs +++ b/src/bin/dbgen.rs @@ -6,7 +6,7 @@ fn main() { let args = Args::from_args(); if let Err(err) = run(args) { eprintln!("{}\n", err); - for (e, i) in err.iter_causes().zip(1..) { + for (e, i) in err.chain().zip(1..) { eprintln!("{:=^80}\n{}\n", format!(" ERROR CAUSE #{} ", i), e); } exit(1); diff --git a/src/cli.rs b/src/cli.rs index bf9ff46..64be164 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -6,14 +6,14 @@ use crate::{ parser::{QName, Template}, }; +use anyhow::{bail, Context, Error}; use chrono_tz::Tz; use data_encoding::{DecodeError, DecodeKind, HEXLOWER_PERMISSIVE}; -use failure::{Error, Fail, ResultExt}; use flate2::write::GzEncoder; use muldiv::MulDiv; use pbr::{MultiBar, Units}; use rand::{ - rngs::{EntropyRng, StdRng}, + rngs::{OsRng, StdRng}, Rng, RngCore, SeedableRng, }; use rayon::{ @@ -22,8 +22,9 @@ use rayon::{ }; use serde_derive::Deserialize; use std::{ + error, fs::{create_dir_all, read_to_string, File}, - io::{self, BufWriter, Write}, + io::{self, stdin, BufWriter, Read, Write}, path::{Path, PathBuf}, str::FromStr, sync::atomic::{AtomicBool, AtomicUsize, Ordering}, @@ -36,134 +37,78 @@ use xz2::write::XzEncoder; /// Arguments to the `dbgen` CLI program. #[derive(StructOpt, Debug, Deserialize)] #[serde(default)] -#[structopt(raw(long_version = "::FULL_VERSION"))] +#[structopt(long_version(crate::FULL_VERSION))] pub struct Args { /// Keep the qualified name when writing the SQL statements. - #[structopt(long = "qualified", help = "Keep the qualified name when writing the SQL statements")] + #[structopt(long)] pub qualified: bool, /// Override the table name. - #[structopt(short = "t", long = "table-name", help = "Override the table name")] + #[structopt(short, long)] pub table_name: Option, /// Output directory. - #[structopt(short = "o", long = "out-dir", help = "Output directory", parse(from_os_str))] + #[structopt(short, long, parse(from_os_str))] pub out_dir: PathBuf, /// Number of files to generate. - #[structopt( - short = "k", - long = "files-count", - help = "Number of files to generate", - default_value = "1" - )] + #[structopt(short = "k", long, default_value = "1")] pub files_count: u32, /// Number of INSERT statements per file. - #[structopt( - short = "n", - long = "inserts-count", - help = "Number of INSERT statements per file", - default_value = "1" - )] + #[structopt(short = "n", long, default_value = "1")] pub inserts_count: u32, /// Number of rows per INSERT statement. - #[structopt( - short = "r", - long = "rows-count", - help = "Number of rows per INSERT statement", - default_value = "1" - )] + #[structopt(short, long, default_value = "1")] pub rows_count: u32, /// Number of INSERT statements in the last file. - #[structopt( - long = "last-file-inserts-count", - help = "Number of INSERT statements in the last file" - )] + #[structopt(long)] pub last_file_inserts_count: Option, /// Number of rows of the last INSERT statement of the last file. - #[structopt( - long = "last-insert-rows-count", - help = "Number of rows of the last INSERT statement of the last file" - )] + #[structopt(long)] pub last_insert_rows_count: Option, - /// Do not escape backslashes when writing a string. - #[structopt(long = "escape-backslash", help = "Escape backslashes when writing a string")] + /// Ecape backslashes when writing a string. + #[structopt(long)] pub escape_backslash: bool, /// Generation template file. - #[structopt( - short = "i", - long = "template", - help = "Generation template file", - parse(from_os_str) - )] + #[structopt(short = "i", long, parse(from_os_str))] pub template: PathBuf, - /// Random number generator seed. - #[structopt( - short = "s", - long = "seed", - help = "Random number generator seed (should have 64 hex digits)", - parse(try_from_str = "seed_from_str") - )] + /// Random number generator seed (should have 64 hex digits). + #[structopt(short, long, parse(try_from_str = seed_from_str))] pub seed: Option<::Seed>, /// Number of jobs to run in parallel, default to number of CPUs. - #[structopt( - short = "j", - long = "jobs", - help = "Number of jobs to run in parallel, default to number of CPUs", - default_value = "0" - )] + #[structopt(short, long, default_value = "0")] pub jobs: usize, /// Random number generator engine - #[structopt( - long = "rng", - help = "Random number generator engine", - raw(possible_values = r#"&["chacha", "hc128", "isaac", "isaac64", "xorshift", "pcg32"]"#), - default_value = "hc128" - )] + #[structopt(long, possible_values(&["chacha", "hc128", "isaac", "isaac64", "xorshift", "pcg32"]), default_value = "hc128")] pub rng: RngName, /// Disable progress bar. - #[structopt(short = "q", long = "quiet", help = "Disable progress bar")] + #[structopt(short, long)] pub quiet: bool, - /// Timezone - #[structopt(long = "time-zone", help = "Time zone used for timestamps", default_value = "UTC")] + /// Time zone used for timestamps + #[structopt(long, default_value = "UTC")] pub time_zone: Tz, /// Output format - #[structopt( - short = "f", - long = "format", - help = "Output format", - raw(possible_values = r#"&["sql", "csv"]"#), - default_value = "sql" - )] + #[structopt(short, long, possible_values(&["sql", "csv"]), default_value = "sql")] pub format: FormatName, - /// Output compression - #[structopt( - short = "c", - long = "compress", - help = "Compress data output", - raw(possible_values = r#"&["gzip", "gz", "xz", "zstd", "zst"]"#) - )] + /// Compress data output + #[structopt(short, long, possible_values(&["gzip", "gz", "xz", "zstd", "zst"]))] pub compression: Option, - /// Output compression level - #[structopt( - long = "compress-level", - help = "Compression level (0-9 for gzip and xz, 1-21 for zstd)", - default_value = "6" - )] + /// Compression level (0-9 for gzip and xz, 1-21 for zstd) + #[structopt(long, default_value = "6")] pub compress_level: u8, } @@ -215,10 +160,10 @@ trait PathResultExt { fn with_path(self, path: &Path) -> Result; } -impl PathResultExt for Result { +impl PathResultExt for Result { type Ok = T; fn with_path(self, path: &Path) -> Result { - Ok(self.with_context(|_| format!("with file {}...", path.display()))?) + self.with_context(|| format!("with file {}...", path.display())) } } @@ -231,7 +176,13 @@ static WRITTEN_SIZE: AtomicUsize = AtomicUsize::new(0); /// Runs the CLI program. pub fn run(args: Args) -> Result<(), Error> { - let input = read_to_string(&args.template).context("failed to read template")?; + let input = if args.template != Path::new("-") { + read_to_string(&args.template) + } else { + let mut buf = String::new(); + stdin().read_to_string(&mut buf).map(move |_| buf) + } + .context("failed to read template")?; let template = Template::parse(&input)?; let pool = ThreadPoolBuilder::new() @@ -269,7 +220,7 @@ pub fn run(args: Args) -> Result<(), Error> { env.write_schema(&template.content)?; - let meta_seed = args.seed.unwrap_or_else(|| EntropyRng::new().gen()); + let meta_seed = args.seed.unwrap_or_else(|| OsRng.gen()); let show_progress = !args.quiet; if show_progress { println!("Using seed: {}", HEXLOWER_PERMISSIVE.encode(&meta_seed)); @@ -358,7 +309,7 @@ impl FromStr for RngName { "isaac64" => RngName::Isaac64, "xorshift" => RngName::XorShift, "pcg32" => RngName::Pcg32, - _ => failure::bail!("Unsupported RNG {}", name), + _ => bail!("Unsupported RNG {}", name), }) } } @@ -392,7 +343,7 @@ impl FromStr for FormatName { Ok(match name { "sql" => FormatName::Sql, "csv" => FormatName::Csv, - _ => failure::bail!("Unsupported format {}", name), + _ => bail!("Unsupported output format {}", name), }) } } @@ -433,7 +384,7 @@ impl FromStr for CompressionName { "gzip" | "gz" => CompressionName::Gzip, "xz" => CompressionName::Xz, "zstd" | "zst" => CompressionName::Zstd, - _ => failure::bail!("Unsupported format {}", name), + _ => bail!("Unsupported compression format {}", name), }) } } diff --git a/src/error.rs b/src/error.rs index d1d40f4..5adba9c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,67 +1,61 @@ //! Error types for the `dbgen` library. -use crate::parser::Function; -use failure::{Backtrace, Context, Fail}; -use std::fmt; +use crate::parser::{Function, Rule}; +use thiserror::Error as ThisError; -/// Kinds of errors produced by the `dbgen` library. -#[derive(Fail, Debug, Clone, PartialEq, Eq)] -//#[non_exhaustive] -pub enum ErrorKind { +/// Errors produced by the `dbgen` library. +#[derive(ThisError, Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum Error { /// Failed to parse template. - #[fail(display = "failed to parse template")] - ParseTemplate, + #[error("failed to parse template")] + ParseTemplate { + #[from] + source: pest::error::Error, + }, /// Unknown SQL function. - #[fail(display = "unknown function '{}'", 0)] + #[error("unknown function '{0}'")] UnknownFunction( /// The name of the unknown SQL function. String, ), /// Integer is too big. - #[fail(display = "integer '{}' is too big", 0)] + #[error("integer '{0}' is too big")] IntegerOverflow( /// The string representation of the expression that produced the overflow. String, ), /// Not enough arguments provided to the SQL function. - #[fail(display = "not enough arguments to function {}", 0)] + #[error("not enough arguments to function {0}")] NotEnoughArguments( /// The SQL function causing the error. Function, ), /// Invalid regex. - #[fail(display = "invalid regex {}", 0)] - InvalidRegex( + #[error("invalid regex {pattern}")] + InvalidRegex { /// The regex pattern. - String, - ), + pattern: String, + /// Source of error + source: rand_regex::Error, + }, /// Unknown regex flag. - #[fail(display = "unknown regex flag {}", 0)] + #[error("unknown regex flag {0}")] UnknownRegexFlag( /// The regex flag. char, ), - /// Unsupported regex element (e.g. `\b`) - #[fail(display = "unsupported regex element: '{}'", 0)] - UnsupportedRegexElement( - /// The regex element. - String, - ), - /// Invalid argument type. /// /// If this error is encountered during compilation phase, the error will be /// ignored and the function will be kept in raw form. - #[fail( - display = "invalid argument type: in function {}, argument #{} should be a {}", - name, index, expected - )] + #[error("invalid argument type: in function {name}, argument #{index} should be a {expected}")] InvalidArgumentType { /// The SQL function causing the error. name: Function, @@ -72,7 +66,7 @@ pub enum ErrorKind { }, /// Invalid arguments. - #[fail(display = "invalid arguments: in function {}, assertion failed: {}", name, cause)] + #[error("invalid arguments: in function {name}, assertion failed: {cause}")] InvalidArguments { /// The SQL function causing the error. name: Function, @@ -81,68 +75,11 @@ pub enum ErrorKind { }, /// The timestamp string is invalid - #[fail(display = "invalid timestamp '{}'", 0)] - InvalidTimestampString( + #[error("invalid timestamp '{timestamp}'")] + InvalidTimestampString { /// The literal which is in the wrong format. - String, - ), - - /// Failed to write the SQL `CREATE TABLE` schema file. - #[fail(display = "failed to write SQL schema")] - WriteSqlSchema, - - /// Failed to write the SQL data file. - #[fail(display = "failed to write SQL data")] - WriteSqlData, - - /// Failed to write an SQL value. - #[fail(display = "failed to write SQL value")] - WriteSqlValue, - - #[doc(hidden)] - #[fail(display = "(placeholder)")] - __NonExhaustive, -} - -/// An error produced by the `dbgen` library. -#[derive(Debug)] -pub struct Error { - inner: Context, -} - -impl Fail for Error { - fn cause(&self) -> Option<&dyn Fail> { - self.inner.cause() - } - - fn backtrace(&self) -> Option<&Backtrace> { - self.inner.backtrace() - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.inner.fmt(f) - } -} - -impl Error { - /// The kind of this error. - pub fn kind(&self) -> &ErrorKind { - self.inner.get_context() - } -} - -impl From for Error { - fn from(kind: ErrorKind) -> Self { - Self { - inner: Context::new(kind), - } - } -} - -impl From> for Error { - fn from(inner: Context) -> Self { - Self { inner } - } + timestamp: String, + /// Source of the error. + source: chrono::format::ParseError, + }, } diff --git a/src/eval.rs b/src/eval.rs index 8d6d93e..04eb8ad 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,17 +1,17 @@ //! Evaluating compiled expressions into values. use crate::{ - error::{Error, ErrorKind}, + error::Error, parser::{Expr, Function}, value::{Number, TryFromValue, Value, TIMESTAMP_FORMAT}, }; use chrono::{NaiveDateTime, TimeZone}; use chrono_tz::Tz; -use failure::ResultExt; use rand::{ - distributions::{self, Uniform}, + distributions::{Bernoulli, BernoulliError}, Rng, RngCore, }; +use rand_distr::{LogNormal, NormalError, Uniform}; use std::{borrow::Cow, cmp::Ordering, fmt}; use zipf::ZipfDistribution; @@ -125,9 +125,9 @@ enum C { /// Zipfian distribution. RandZipf(ZipfDistribution), /// Log-normal distribution. - RandLogNormal(distributions::LogNormal), + RandLogNormal(LogNormal), /// Bernoulli distribution for `bool` (i.e. a weighted random boolean). - RandBool(distributions::Bernoulli), + RandBool(Bernoulli), /// Random f32 with uniform bit pattern RandFiniteF32(Uniform), /// Random f64 with uniform bit pattern @@ -153,7 +153,7 @@ impl AsValue for Compiled { } /// Extracts a single argument in a specific type. -fn arg<'a, T, E>(name: Function, args: &'a [E], index: usize, default: Option) -> Result +fn arg<'a, T, E>(name: Function, args: &'a [E], index: usize, default: Option) -> Result where T: TryFromValue<'a>, E: AsValue, @@ -161,14 +161,14 @@ where if let Some(arg) = args.get(index) { arg.as_value() .and_then(T::try_from_value) - .ok_or(ErrorKind::InvalidArgumentType { + .ok_or(Error::InvalidArgumentType { name, index, expected: T::NAME, }) } else { - #[cfg_attr(feature = "cargo-clippy", allow(clippy::or_fun_call))] // false positive, this is cheap - default.ok_or(ErrorKind::NotEnoughArguments(name)) + #[allow(clippy::or_fun_call)] // false positive, this is cheap + default.ok_or(Error::NotEnoughArguments(name)) } } @@ -179,14 +179,13 @@ where E: AsValue, { args.iter().enumerate().map(move |(index, arg)| { - arg.as_value().and_then(T::try_from_value).ok_or_else(|| { - ErrorKind::InvalidArgumentType { + arg.as_value() + .and_then(T::try_from_value) + .ok_or(Error::InvalidArgumentType { name, index, expected: T::NAME, - } - .into() - }) + }) }) } @@ -205,8 +204,8 @@ impl CompileContext { .collect::, _>>()?; match compile_function(self, name, &args) { Ok(c) => c.0, - Err(e) => match e.kind() { - ErrorKind::InvalidArgumentType { .. } => C::RawFunction { name, args }, + Err(e) => match e { + Error::InvalidArgumentType { .. } => C::RawFunction { name, args }, _ => return Err(e), }, } @@ -316,13 +315,10 @@ impl AsValue for Value { /// Compiles a function with some value-like objects as input. pub fn compile_function(ctx: &CompileContext, name: Function, args: &[impl AsValue]) -> Result { macro_rules! require { - (@false, $($fmt:tt)+) => { - return Err(ErrorKind::InvalidArguments { name, cause: format!($($fmt)+) }.into()); - }; ($e:expr, $($fmt:tt)+) => { - #[cfg_attr(feature = "cargo-clippy", allow(clippy::neg_cmp_op_on_partial_ord))] { + #[allow(clippy::neg_cmp_op_on_partial_ord)] { if !$e { - require!(@false, $($fmt)+); + return Err(Error::InvalidArguments { name, cause: format!($($fmt)+) }); } } }; @@ -346,7 +342,7 @@ pub fn compile_function(ctx: &CompileContext, name: Function, args: &[impl AsVal } else if let (Some(a), Some(b)) = (lower.to::(), upper.to::()) { Ok(Compiled(C::RandUniformI64(Uniform::new(a, b)))) } else { - Err(ErrorKind::IntegerOverflow(format!("rand.range({}, {})", lower, upper)).into()) + Err(Error::IntegerOverflow(format!("rand.range({}, {})", lower, upper))) } } @@ -359,7 +355,10 @@ pub fn compile_function(ctx: &CompileContext, name: Function, args: &[impl AsVal } else if let (Some(a), Some(b)) = (lower.to::(), upper.to::()) { Ok(Compiled(C::RandUniformI64(Uniform::new_inclusive(a, b)))) } else { - Err(ErrorKind::IntegerOverflow(format!("rand.range_inclusive({}, {})", lower, upper)).into()) + Err(Error::IntegerOverflow(format!( + "rand.range_inclusive({}, {})", + lower, upper + ))) } } @@ -388,13 +387,22 @@ pub fn compile_function(ctx: &CompileContext, name: Function, args: &[impl AsVal Function::RandLogNormal => { let mean = arg(name, args, 0, None)?; let std_dev = arg::(name, args, 1, None)?.abs(); - Ok(Compiled(C::RandLogNormal(distributions::LogNormal::new(mean, std_dev)))) + Ok(Compiled(C::RandLogNormal(LogNormal::new(mean, std_dev).map_err( + |NormalError::StdDevTooSmall| Error::InvalidArguments { + name, + cause: format!("{} (std_dev) >= 0", std_dev), + }, + )?))) } Function::RandBool => { let p = arg(name, args, 0, None)?; - require!(0.0 <= p && p <= 1.0, "{} between 0 and 1", p); - Ok(Compiled(C::RandBool(distributions::Bernoulli::new(p)))) + Ok(Compiled(C::RandBool(Bernoulli::new(p).map_err( + |BernoulliError::InvalidProbability| Error::InvalidArguments { + name, + cause: format!("0 <= {} (p) <= 1", p), + }, + )?))) } Function::RandFiniteF32 => Ok(Compiled(C::RandFiniteF32(Uniform::new(0, 0xff00_0000)))), @@ -486,7 +494,10 @@ pub fn compile_function(ctx: &CompileContext, name: Function, args: &[impl AsVal let tz = ctx.time_zone; let timestamp = tz .datetime_from_str(input, TIMESTAMP_FORMAT) - .with_context(|_| ErrorKind::InvalidTimestampString(input.to_owned()))? + .map_err(|source| Error::InvalidTimestampString { + timestamp: input.to_owned(), + source, + })? .naive_utc(); Ok(Compiled(C::Constant(Value::Timestamp(timestamp, tz)))) } @@ -498,14 +509,17 @@ pub fn compile_function(ctx: &CompileContext, name: Function, args: &[impl AsVal Some(i) => { let tz = input[i..] .parse::() - .map_err(|_| ErrorKind::InvalidTimestampString(input.to_owned()))?; + .map_err(|cause| Error::InvalidArguments { name, cause })?; input = input[..i].trim_end(); tz } }; let timestamp = tz .datetime_from_str(input, TIMESTAMP_FORMAT) - .with_context(|_| ErrorKind::InvalidTimestampString(input.to_owned()))? + .map_err(|source| Error::InvalidTimestampString { + timestamp: input.to_owned(), + source, + })? .naive_utc(); Ok(Compiled(C::Constant(Value::Timestamp(timestamp, tz)))) } @@ -549,15 +563,17 @@ fn compile_regex_generator(regex: &str, flags: &str, max_repeat: u32) -> Result< 'm' => parser.multi_line(true), 's' => parser.dot_matches_new_line(true), 'U' => parser.swap_greed(true), - _ => return Err(ErrorKind::UnknownRegexFlag(flag).into()), + _ => return Err(Error::UnknownRegexFlag(flag)), }; } - let hir = parser - .build() - .parse(regex) - .with_context(|_| ErrorKind::InvalidRegex(regex.to_owned()))?; - let gen = - rand_regex::Regex::with_hir(hir, max_repeat).with_context(|_| ErrorKind::InvalidRegex(regex.to_owned()))?; + let hir = parser.build().parse(regex).map_err(|source| Error::InvalidRegex { + pattern: regex.to_owned(), + source: source.into(), + })?; + let gen = rand_regex::Regex::with_hir(hir, max_repeat).map_err(|source| Error::InvalidRegex { + pattern: regex.to_owned(), + source, + })?; Ok(gen) } diff --git a/src/parser.rs b/src/parser.rs index 8baf3c7..1db00be 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,12 +1,9 @@ //! Template parser. -use self::derived::{Rule, TemplateParser}; -use crate::{ - error::{Error, ErrorKind}, - value::Value, -}; +pub(crate) use self::derived::Rule; +use self::derived::TemplateParser; +use crate::{error::Error, value::Value}; -use failure::ResultExt; use pest::{iterators::Pairs, Parser}; use std::{collections::HashMap, fmt}; @@ -54,7 +51,7 @@ impl QName { /// Parses a qualified name pub fn parse(input: &str) -> Result { - let mut pairs = TemplateParser::parse(Rule::qname, input).context(ErrorKind::ParseTemplate)?; + let mut pairs = TemplateParser::parse(Rule::qname, input)?; Ok(Self::from_pairs(pairs.next().unwrap().into_inner())) } @@ -186,7 +183,7 @@ fn is_ident_char(c: char) -> bool { impl Template { /// Parses a raw string into a structured template. pub fn parse(input: &str) -> Result { - let pairs = TemplateParser::parse(Rule::create_table, input).context(ErrorKind::ParseTemplate)?; + let pairs = TemplateParser::parse(Rule::create_table, input)?; let mut name = None; let mut alloc = Allocator::default(); @@ -521,8 +518,7 @@ impl Allocator { fn parse_number(input: &str) -> Result { match input.get(..2) { Some("0x") | Some("0X") => { - let number = - u64::from_str_radix(&input[2..], 16).with_context(|_| ErrorKind::IntegerOverflow(input.to_owned()))?; + let number = u64::from_str_radix(&input[2..], 16).map_err(|_| Error::IntegerOverflow(input.to_owned()))?; return Ok(number.into()); } _ => {} @@ -568,7 +564,7 @@ macro_rules! define_function { fn from_name(name: String) -> Result { Ok(match &*name { $($fs => $F::$fi,)* - _ => return Err(ErrorKind::UnknownFunction(name).into()), + _ => return Err(Error::UnknownFunction(name)), }) } diff --git a/src/schemagen_cli.rs b/src/schemagen_cli.rs index d85ef62..6045ee8 100644 --- a/src/schemagen_cli.rs +++ b/src/schemagen_cli.rs @@ -1,78 +1,55 @@ //! CLI driver of `dbschemagen`. use crate::parser::QName; +use anyhow::{bail, Error}; use data_encoding::HEXLOWER_PERMISSIVE; -use failure::Error; use rand::{ - distributions::{Distribution, LogNormal, Pareto, WeightedIndex}, - rngs::{EntropyRng, StdRng}, + rngs::{OsRng, StdRng}, seq::SliceRandom, Rng, RngCore, SeedableRng, }; +use rand_distr::{weighted::WeightedIndex, Distribution, LogNormal, Pareto}; use std::{ collections::{BTreeSet, HashSet}, fmt::Write, iter::repeat_with, str::FromStr, }; -use structopt::StructOpt; +use structopt::{clap::AppSettings, StructOpt}; /// Arguments to the `dbschemagen` CLI program. #[derive(StructOpt, Debug)] -#[structopt(raw( - setting = "structopt::clap::AppSettings::TrailingVarArg", - long_version = "::FULL_VERSION" -))] +#[structopt(setting(AppSettings::TrailingVarArg), long_version(crate::FULL_VERSION))] pub struct Args { /// Schema name. - #[structopt(short = "s", long = "schema-name", help = "Schema name")] + #[structopt(short, long)] pub schema_name: String, /// Estimated total database dump size in bytes. - #[structopt(short = "z", long = "size", help = "Estimated total database dump size in bytes")] + #[structopt(short = "z", long)] pub size: f64, /// Number of tables to generate - #[structopt(short = "t", long = "tables-count", help = "Number of tables to generate")] + #[structopt(short, long)] pub tables_count: u32, /// SQL dialect. - #[structopt( - short = "d", - long = "dialect", - help = "SQL dialect", - raw(possible_values = r#"&["mysql", "postgresql", "sqlite"]"#) - )] + #[structopt(short, long, possible_values(&["mysql", "postgresql", "sqlite"]))] pub dialect: Dialect, /// Number of INSERT statements per file. - #[structopt( - short = "n", - long = "inserts-count", - help = "Number of INSERT statements per file", - default_value = "1000" - )] + #[structopt(short = "n", long, default_value = "1000")] pub inserts_count: u64, /// Number of rows per INSERT statement. - #[structopt( - short = "r", - long = "rows-count", - help = "Number of rows per INSERT statement", - default_value = "100" - )] + #[structopt(short, long, default_value = "100")] pub rows_count: u64, - /// Random number generator seed. - #[structopt( - long = "seed", - help = "Random number generator seed (should have 64 hex digits)", - parse(try_from_str = "crate::cli::seed_from_str") - )] + /// Random number generator seed (should have 64 hex digits). + #[structopt(long, parse(try_from_str = crate::cli::seed_from_str))] pub seed: Option<::Seed>, /// Additional arguments passed to every `dbgen` invocation - #[structopt(help = "Additional arguments passed to every `dbgen` invocation")] pub args: Vec, } @@ -94,7 +71,7 @@ impl FromStr for Dialect { "mysql" => Dialect::MySQL, "postgresql" => Dialect::PostgreSQL, "sqlite" => Dialect::SQLite, - _ => failure::bail!("Unsupported SQL dialect {}", dialect), + _ => bail!("Unsupported SQL dialect {}", dialect), }) } } @@ -311,7 +288,7 @@ fn gen_column(dialect: Dialect, rng: &mut dyn RngCore) -> Column { } struct IndexAppender<'a> { - index_count_distr: Pareto, + index_count_distr: Pareto, index_distr: WeightedIndex, columns: &'a [Column], index_sets: HashSet>, @@ -320,7 +297,7 @@ struct IndexAppender<'a> { impl<'a> IndexAppender<'a> { fn new(columns: &'a [Column]) -> Self { Self { - index_count_distr: Pareto::new(1.0, 1.6), + index_count_distr: Pareto::new(1.0, 1.6).unwrap(), index_distr: WeightedIndex::new(columns.iter().map(|col| col.neg_log2_prob.min(32.0))).unwrap(), columns, index_sets: HashSet::new(), @@ -337,8 +314,7 @@ impl<'a> IndexAppender<'a> { is_primary_key: bool, ) { let index_count = (self.index_count_distr.sample(rng) as usize).min(12); - let index_set = self - .index_distr + let index_set = (&self.index_distr) .sample_iter(&mut rng) .take(index_count) .collect::>(); @@ -385,7 +361,7 @@ struct Table { fn gen_table(dialect: Dialect, rng: &mut dyn RngCore, target_size: f64) -> Table { let mut schema = String::from("CREATE TABLE _ (\n"); - let columns_count = (LogNormal::new(2.354_259_469_228_055, 0.75).sample(rng) as usize).max(1); + let columns_count = (LogNormal::new(2.354_259_469_228_055, 0.75).unwrap().sample(rng) as usize).max(1); let columns = { let rng2 = &mut *rng; repeat_with(move || gen_column(dialect, rng2)) @@ -431,7 +407,7 @@ fn gen_tables<'a>( total_target_size: f64, tables_count: u32, ) -> impl Iterator + 'a { - let distr = Pareto::new(1.0, 1.16); + let distr = Pareto::new(1.0, 1.16).unwrap(); let relative_sizes = distr .sample_iter(&mut rng) .take(tables_count as usize) @@ -461,7 +437,7 @@ pub fn print_script(args: &Args) { let schema_name = QName::parse(&args.schema_name).expect("invalid schema name"); let quoted_schema_name = shlex::quote(&args.schema_name); - let meta_seed = args.seed.unwrap_or_else(|| EntropyRng::new().gen()); + let meta_seed = args.seed.unwrap_or_else(|| OsRng.gen()); println!( "#!/bin/sh\n\ # generated by dbschemagen v{} ({}), using seed {}\n\n\ @@ -474,6 +450,8 @@ pub fn print_script(args: &Args) { schema_name.unique_name(), ); + let exe_suffix = if cfg!(windows) { ".exe" } else { "" }; + let rng = StdRng::from_seed(meta_seed); let extra_args = args.args.iter().map(|s| shlex::quote(s)).collect::>().join(" "); let rows_count_per_file = args.rows_count * args.inserts_count; @@ -494,12 +472,13 @@ pub fn print_script(args: &Args) { }; println!( "# table: s{}, rows count: {}, estimated size: {}\n\ - dbgen -i /dev/stdin -o . -s {} -t {}.s{} -n {} -r {} -k {} \ + dbgen{} -i - -o . -s {} -t {}.s{} -n {} -r {} -k {} \ --last-file-inserts-count {} --last-insert-rows-count {} \ {} < a.partial_cmp(b), (Value::Interval(a), Value::Interval(b)) => a.partial_cmp(b), _ => { - return Err(ErrorKind::InvalidArguments { + return Err(Error::InvalidArguments { name, cause: format!("cannot compare {} with {}", self, other), - } - .into()); + }); } }) } @@ -266,11 +262,10 @@ impl Value { Value::Interval(try_or_overflow!(a.checked_add(*b), "{} + {}", a, b)) } _ => { - return Err(ErrorKind::InvalidArguments { + return Err(Error::InvalidArguments { name: Function::Add, cause: format!("cannot add {} to {}", self, other), - } - .into()); + }); } }) } @@ -292,11 +287,10 @@ impl Value { Value::Interval(try_or_overflow!(a.checked_sub(*b), "{} + {}", a, b)) } _ => { - return Err(ErrorKind::InvalidArguments { + return Err(Error::InvalidArguments { name: Function::Sub, cause: format!("cannot subtract {} from {}", self, other), - } - .into()); + }); } }) } @@ -310,11 +304,10 @@ impl Value { Value::Interval(try_or_overflow!(mult_res.to::(), "{} microseconds", mult_res)) } _ => { - return Err(ErrorKind::InvalidArguments { + return Err(Error::InvalidArguments { name: Function::Mul, cause: format!("cannot multiply {} with {}", self, other), - } - .into()); + }); } }) } @@ -333,11 +326,10 @@ impl Value { Value::Interval(try_or_overflow!(mult_res.to::(), "{} microseconds", mult_res)) } _ => { - return Err(ErrorKind::InvalidArguments { + return Err(Error::InvalidArguments { name: Function::FloatDiv, cause: format!("cannot divide {} by {}", self, other), - } - .into()); + }); } }) } diff --git a/tests/check.rs b/tests/check.rs index 98db90c..1858ebf 100644 --- a/tests/check.rs +++ b/tests/check.rs @@ -1,6 +1,6 @@ +use anyhow::Error; use dbgen::cli::{run, Args}; use diff::{lines, Result as DiffResult}; -use failure::Error; use serde_json::from_reader; use std::{ fs::{read, read_dir, remove_file, File}, diff --git a/tests/data/rand-finite-float/result.1.sql b/tests/data/rand-finite-float/result.1.sql index 0f2d7a8..95edb64 100644 --- a/tests/data/rand-finite-float/result.1.sql +++ b/tests/data/rand-finite-float/result.1.sql @@ -1,101 +1,101 @@ INSERT INTO result VALUES -(1, 90672.484375, 1.1339103227112086e22), -(2, -3.9885693578263373e-16, 7.833575980360231e-174), -(3, 7.240773990175674e22, 1.4197898344540396e280), -(4, 59127382016.0, 6.616223591746987e-236), -(5, -4.2604917653137e17, 4.362379751853048e133), -(6, -271989.78125, 3.251462614608522e-186), -(7, -2234509426688.0, 2.724018576451632e-22), -(8, 4.575370547937472e29, 3.748573372495495e154), -(9, -0.0014323251089081168, 5.4988323481225707e45), -(10, -3.28562618062905e-23, 2.206077893973367e-168), -(11, -13047437459456.0, 4.166804169107403e-100), -(12, 2.5795822958546562e36, 1.632393225116654e-87), -(13, 5.483646869919312e-9, 21249691.171849478), -(14, -3.853022526155832e-35, 5.947947743978342e-182), -(15, 3.1382959907484334e31, -7.795565200938266e130), -(16, -1.0497456421507568e-27, -1.0592991141145154e17), -(17, 1.5472405742531271e38, -3.1290732865309286e174), -(18, 184289.1875, -3.896294566591252e-73), -(19, -9.706033661098786e36, -5.763447382303127e147), -(20, -1.3068177182745666e-12, -1.5322762544893857e184), -(21, 20397.857421875, 8.851270437374964e-244), -(22, -8.264244104892828e-24, -4.628369298342482e282), -(23, -0.0017546163871884346, 714606769936.4602), -(24, -4.840370326775201e-9, -5.433999502097366e211), -(25, -9.99207236423123e17, -2.310721571800414e-258), -(26, 3.712962536102852e29, 4.136223942330634e-183), -(27, -1.2318039616064832e24, 1.324698983551158e-265), -(28, -0.0039032120257616043, 1.148385179919965e164), -(29, -11832683134976.0, 1.0086204532409517e-46), -(30, 1.839200552239864e23, -1.8628866409044736e17), -(31, -3.7525185490257055e-19, 1.4242891264403062e-136), -(32, -2.478781159400999e-31, -6.393287095280547e-9), -(33, -118.38725280761719, 8039257107.044941), -(34, 2.9621754525204186e-37, 8.568219555719027e-28), -(35, -6.4958161666976e25, 4.696497143717539e-150), -(36, -3.534039077491801e-27, 2.828365085092237e-63), -(37, 0.46201908588409424, 2.4086920863229868e221), -(38, -1730867200.0, 1.7040597490515657e169), -(39, -2.3207548497923827e20, 3.195767411252744e189), -(40, 8.96008259527905e-22, -7.261114640705759e-237), -(41, 9.793616827353538e-19, 2.611466464958359e-238), -(42, 7.116422136891493e-34, -1.248617608609914e-101), -(43, -32209061888.0, -9.517551565433224e-94), -(44, 2.890690087110959e-21, 2.5302639567197774e179), -(45, -7.360597908956379e-9, 1.2453982176648428e186), -(46, 4.07431450307689e-16, 2.0173561844416902e155), -(47, 1.3118930086067658e25, -9.347434650324013e-157), -(48, -3.099216575121671e23, 0.17191740799896224), -(49, 513.27734375, 6.169315103399442e138), -(50, 1.6981207957288199e31, 1.553235595276201e-266), -(51, -1.1796304631116595e17, 2.059609972927675e-184), -(52, -986501056.0, -3.1945070427158674e-282), -(53, 58292476.0, -1.06972082130298e141), -(54, -1.4203994960976022e-18, 8.749596682028774e288), -(55, -7.828941306797788e-6, 1.5344428407500372e-54), -(56, -1.0219106066133419e-31, 3.874525697186665e-104), -(57, -0.00022248682216741145, -2.0199254934565108e-291), -(58, 3.966689173309779e-15, -3.9935644990683073e-134), -(59, 7.429580667168139e25, 2.896614989721052e185), -(60, -2.0374230838154363e18, 1.403122282667301e243), -(61, 1.2594012847190817e36, -6.4432442789482024e-27), -(62, -3.597980348451771e21, 1.27588639295836e-29), -(63, 9.675402378062053e-20, 5.729772407943651e-204), -(64, -2.6915499302243056e32, 1.3923459825706752e-286), -(65, 1.1558364427076795e-36, -1.6745067879870783e130), -(66, 1.9405875963038245e-25, 3.512491676602063e136), -(67, -2678.02294921875, 1.845593182341988e168), -(68, 83333.359375, -2.1158726438057665e-113), -(69, 1.1352855317412242e-27, -1.1752556245355497e180), -(70, -1.3565529244930025e-31, 7.10625218667836e169), -(71, -6.568974099110029e-35, 9.128038292326728e78), -(72, 749965632.0, 26.100409017090264), -(73, -3.192422940408775e-34, -1.8827271106731403e-283), -(74, -1.6730553001546006e-18, 8.646951925107533e-237), -(75, -131585040384.0, -2.900246076083441e201), -(76, 8.313116688543911e-20, -3.583701803049338e110), -(77, 3.0312861599103097e27, -1.033105464477069e-228), -(78, -2.8692661580009826e-9, -3.1608025039353693e-114), -(79, 1.6661872450640658e-6, -5.246588704751609e-101), -(80, 3.429970765775033e-24, -3.853122356887104e-66), -(81, -8.590454569912254e-9, -1603157.5510638105), -(82, 1.2052242153515585e-22, 1.762190988544563e259), -(83, -2.4019966571309226e-10, 3.442827619789515e305), -(84, -7.90335033212135e26, 3.688915544853158e-9), -(85, 1.1506715519726964e20, 1.856185825363751e183), -(86, -1.582902679030045e19, 4.567456213728061e282), -(87, 1.706448342340631e25, 2.994565385602456e93), -(88, 16.967838287353516, 3.441059180697452e299), -(89, -1.7456716591147254e18, 1.6129647092582622e-91), -(90, -1.2552967573911634e18, 1.4062936734520358e200), -(91, -7.6454928587424115e-34, -7.442819471086665e-292), -(92, -5.590406067755571e-13, 5.062064109209187e-302), -(93, 3.327976025148621e17, 1.0298670777918947e99), -(94, -41.087646484375, -2.4032856755403114e-280), -(95, -3.036375000994206e21, -1.1995223344396541e-207), -(96, -2.194237901724742e24, -1.3484774165468417e299), -(97, 0.9569990634918213, 2.521787143855416e-197), -(98, 1.374498209931269e-18, -3.0250956456896433e279), -(99, -1.031601547534211e31, -2.0761968006586557e-255), -(100, 1.9719611301790482e19, 5.050829248467284e266); +(1, 49133.83984375, -3.813889467250918e238), +(2, -5.977244138314138e17, 5.294720482563911e-99), +(3, 9.286366971648003e33, -3.1316173860921434e-29), +(4, -2.1739981861751414e-20, 3.907048172406935e-61), +(5, -2.184007071587033e-20, -5.945412480403958e79), +(6, 7.784046155520655e-26, -6.879996482333769e-241), +(7, -2.456674203506325e-19, 2.7300922109750375e-140), +(8, 9.910603864007347e29, -2.2434099553962055e100), +(9, -1.210376001785158e-17, 1.406586385908381e-182), +(10, -9.536219636174792e-8, 0.1326502904886367), +(11, 1.8875982761383057, 1.0108493260475293e194), +(12, -1259382658564096.0, 5.623851294419873e-23), +(13, 0.0011162766022607684, -9.597134284269515e256), +(14, 1.500314056164417e31, -9.690481608076253e-203), +(15, 7096604672.0, -1.8998679869410044e-31), +(16, 706067392.0, -5.543257580874108e-94), +(17, -134135.375, 5.663759078686202e252), +(18, 10913.7841796875, -1.0379454162522571e-184), +(19, -576.1181030273438, -7.066230452173579e90), +(20, 5.934275605336063e-18, -6.145726334946315e49), +(21, 9.102771377162193e-23, 3.2124338475965473e-102), +(22, -3181247379537920.0, 3.1097507033711202e137), +(23, 0.0007852199487388134, 5.914305281469588e142), +(24, 0.09578239917755127, -8.414799120280378e30), +(25, 0.0184929259121418, -3.0454184372538902e-238), +(26, -1.0186080157004806e-26, 7.606695017458726e-279), +(27, -0.0003083392512053251, 6.28085483148558e-158), +(28, -2.119727800599168e-27, 3.8980753657714144e-242), +(29, 1.5547067827583514e37, -4.3459563429513854e257), +(30, -7.721427343765931e29, 5.859420584939821e94), +(31, 1380957945856.0, 2.5361040269535987e-144), +(32, 428.7649841308594, 1.701852412575849e124), +(33, -8046753792.0, -2.493425495829749e-284), +(34, -8.881375800534785e29, 2.7607383014690537e-171), +(35, 5.66812348548136e23, 2.0796941798174546e-269), +(36, -5.0657926886508295e-20, 1.4389930370466802e-300), +(37, 4.386112362197991e-9, 6.349846828539735e38), +(38, -8.33321917746e28, -2.5656679046112948e-222), +(39, 5.5279176012059186e-14, 8.382800093812241e181), +(40, -706139979776.0, -1.0291320966143156e-287), +(41, 3.4053232491240134e-17, 1.4994901576889996e257), +(42, -2.9457683514699795e-10, 1.8258087596741281e-214), +(43, 1.171951576982487e18, -7.636319983120404e-286), +(44, -1.2396251440880763e-34, 8.87375442297441e-91), +(45, -7.608882774115563e-30, -9.068493840759427e-98), +(46, -3.9893500676003594e18, 1.2495782771498664e87), +(47, 3.9970216997394725e-20, 1.1843691729393073e-291), +(48, 0.10188473761081696, 3.453546471698074e-88), +(49, -2.746489963042213e30, -8.387594988592028e-106), +(50, 5.430742734808331e21, 3.8941896171293276e-258), +(51, 75291368.0, 1.1589270883203147e-88), +(52, -6.282534171717091e-14, 1.7815104999313686e-299), +(53, 0.001393600134178996, 1.4395579225309e255), +(54, -635978.0, -1.7868881133543302e254), +(55, 1.8519392833496462e-13, -1.1325880097690456e-128), +(56, 1.436792786014666e-33, -1.1143530411355926e-180), +(57, 14725208.0, 6.517631375889509e-87), +(58, -0.07663802057504654, -7.89725476145313e128), +(59, -1.223503739690385e-24, -1.2936875981327437e-116), +(60, -0.06506158411502838, 4.692012669115477e-77), +(61, -1.130866127260858e-25, -5.680696022120995e31), +(62, 1.1772086541677075e-29, 5.13817729648818e148), +(63, -6.868658314493282e33, -1.274885785597697e-140), +(64, -5.048841789090375e-9, -3.5041356184260523e-283), +(65, -13.444188117980957, -3.871549881333396e-146), +(66, -467333600.0, -5.813442580420425e267), +(67, 1.7407725039220168e-8, -2.022001969011936e-306), +(68, -1.3385047897445194e-18, 7.870864288718949e103), +(69, -237415910342656.0, 2.0024777001502007e-306), +(70, 2.0514210924758804e37, 2.6220070931479577e198), +(71, -3.8953108607616133e-19, 7.071871935118972e-68), +(72, 2.559810975844429e-21, -1.3021141223630773e-179), +(73, 1.6730026431532656e20, 8.244984128820534e-236), +(74, 1.0639377364030579e-17, 5.453170336298141e28), +(75, -1.8578003718738856e-12, -6.26436022233049e-158), +(76, 0.18618138134479523, -1.129131575474138e235), +(77, -4.1298424235947095e-8, -9.175061683881854e-140), +(78, 463716253696.0, 1.1880038812836696e-82), +(79, -4.640795950030364e-36, 1.3533114864583417e-130), +(80, 1614853120.0, -8.16376038477991e192), +(81, -1.7151529271794074e18, 6.92605436502678e-208), +(82, 4.800476353904699e-37, -2.0828847088784098e278), +(83, 6.05157937649814e-11, 1.5104316146778642), +(84, -2.8482346469997382e32, -1.379198159727258e250), +(85, 13454331.0, 1.6884509612515232e-100), +(86, 6.6313430302500365e-9, -3.2639606858964533e252), +(87, 1.626113652916123e29, 9.696883445593055e110), +(88, -1.950526780092332e-7, 8.989905794382782e-248), +(89, 9.402792599486015e-19, 8.436595899598161e47), +(90, -5.753484725952148, -1.5249023837007421e168), +(91, -5.4730233176003665e-17, -6.062227240660461e-90), +(92, -1.934440999568634e21, -5.396484039947363e69), +(93, -46209.66015625, 3.590215095511005e241), +(94, 5.093276509439787e19, 1.3563534755578674e38), +(95, 2.5879256471849256e-24, 6.587484924827795e228), +(96, 6.772953228084569e31, 4.1062081333031473e-221), +(97, -0.00002944402331195306, -1.599929808566783e-242), +(98, 3.685313791638566e-21, -5.685779616910464e-244), +(99, 4.929759537141547e27, -1.1209816266586862e241), +(100, -2.564531629640909e16, 4.546209251952802e-50); diff --git a/tests/data/seeded-hc128/result.1.sql b/tests/data/seeded-hc128/result.1.sql index c2caeb2..dc63547 100644 --- a/tests/data/seeded-hc128/result.1.sql +++ b/tests/data/seeded-hc128/result.1.sql @@ -1,101 +1,101 @@ INSERT INTO result VALUES -(1, 4, 5), -(2, 2, 1), -(3, 6, 8), -(4, 4, 2), -(5, 1, 3), +(1, 2, 1), +(2, 3, 2), +(3, 2, 4), +(4, 3, 7), +(5, 1, 8), (6, 8, 1), -(7, 3, 6), -(8, 2, 1), -(9, 9, 4), -(10, 2, 9), -(11, 2, 2), -(12, 1, 2), -(13, 9, 2), -(14, 3, 3), -(15, 3, 1), -(16, 5, 6), -(17, 9, 1), -(18, 3, 1), -(19, 2, 1), -(20, 2, 1), -(21, 4, 1), -(22, 7, 1), -(23, 5, 3), -(24, 0, 3), -(25, 4, 10), -(26, 4, 6), -(27, 1, 4), -(28, 1, 2), -(29, 7, 1), -(30, 2, 8), -(31, 2, 9), -(32, 3, 3), -(33, 2, 1), -(34, 2, 5), -(35, 7, 4), -(36, 8, 3), -(37, 9, 1), -(38, 0, 2), -(39, 6, 9), -(40, 1, 5), -(41, 9, 1), -(42, 0, 4), -(43, 9, 5), -(44, 2, 1), -(45, 7, 1), -(46, 7, 1), -(47, 4, 6), -(48, 9, 9), -(49, 5, 7), -(50, 7, 1), -(51, 5, 5), -(52, 9, 8), -(53, 7, 2), -(54, 6, 3), -(55, 0, 5), -(56, 1, 1), -(57, 2, 1), -(58, 5, 4), +(7, 0, 4), +(8, 4, 1), +(9, 6, 3), +(10, 9, 1), +(11, 1, 1), +(12, 6, 4), +(13, 7, 2), +(14, 2, 2), +(15, 2, 2), +(16, 8, 1), +(17, 7, 8), +(18, 4, 2), +(19, 6, 7), +(20, 0, 1), +(21, 1, 8), +(22, 6, 1), +(23, 6, 3), +(24, 5, 1), +(25, 1, 1), +(26, 2, 6), +(27, 2, 10), +(28, 2, 1), +(29, 1, 5), +(30, 6, 9), +(31, 5, 4), +(32, 1, 6), +(33, 0, 4), +(34, 0, 1), +(35, 6, 3), +(36, 4, 4), +(37, 1, 1), +(38, 0, 1), +(39, 3, 10), +(40, 8, 2), +(41, 9, 9), +(42, 0, 6), +(43, 3, 3), +(44, 7, 1), +(45, 4, 4), +(46, 4, 8), +(47, 7, 2), +(48, 3, 9), +(49, 6, 2), +(50, 9, 1), +(51, 2, 1), +(52, 9, 1), +(53, 8, 1), +(54, 2, 6), +(55, 7, 5), +(56, 5, 3), +(57, 4, 1), +(58, 3, 1), (59, 3, 5), -(60, 1, 4), -(61, 7, 6), -(62, 9, 1), -(63, 8, 3), -(64, 4, 1), -(65, 9, 5), -(66, 5, 1), -(67, 0, 1), -(68, 8, 4), -(69, 3, 10), -(70, 2, 2), -(71, 0, 5), -(72, 8, 1), -(73, 4, 5), -(74, 9, 1), -(75, 7, 1), -(76, 5, 1), -(77, 2, 6), -(78, 8, 8), -(79, 2, 1), -(80, 8, 3), -(81, 6, 2), -(82, 3, 8), -(83, 3, 2), -(84, 6, 9), -(85, 9, 3), -(86, 8, 7), -(87, 1, 5), -(88, 0, 2), -(89, 5, 1), -(90, 4, 1), -(91, 7, 1), -(92, 3, 1), -(93, 8, 1), -(94, 8, 1), -(95, 0, 6), -(96, 6, 3), -(97, 1, 7), -(98, 8, 4), -(99, 0, 4), -(100, 7, 2); +(60, 6, 1), +(61, 5, 9), +(62, 9, 4), +(63, 9, 1), +(64, 5, 4), +(65, 9, 1), +(66, 4, 8), +(67, 8, 3), +(68, 7, 1), +(69, 7, 2), +(70, 8, 1), +(71, 5, 1), +(72, 9, 7), +(73, 7, 6), +(74, 1, 5), +(75, 7, 3), +(76, 1, 1), +(77, 1, 5), +(78, 0, 1), +(79, 7, 1), +(80, 5, 2), +(81, 4, 1), +(82, 5, 2), +(83, 2, 10), +(84, 2, 1), +(85, 5, 8), +(86, 6, 3), +(87, 9, 4), +(88, 6, 7), +(89, 3, 2), +(90, 1, 1), +(91, 5, 1), +(92, 2, 1), +(93, 2, 5), +(94, 7, 1), +(95, 4, 1), +(96, 2, 1), +(97, 2, 1), +(98, 9, 6), +(99, 7, 1), +(100, 5, 10);