From ce55dc4a7506c79b1dbf77adf816cad1a9d566a8 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 8 Jan 2021 23:39:10 +0100 Subject: [PATCH 01/14] Add nonfunctional "prop:rated" as possible search query --- rslib/src/search/parser.rs | 45 +++++++++++++++++++++++++++++++++++ rslib/src/search/sqlwriter.rs | 3 +++ rslib/src/search/writer.rs | 4 ++++ 3 files changed, 52 insertions(+) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index ff7f1ab84..d5ed46266 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -87,6 +87,7 @@ pub enum PropertyKind { Lapses(u32), Ease(f32), Position(u32), + Rated(u32, Option), } #[derive(Debug, PartialEq, Clone)] @@ -369,6 +370,7 @@ fn parse_prop(s: &str) -> ParseResult { tag("lapses"), tag("ease"), tag("pos"), + tag("rated"), ))(s) .map_err(|_| parse_failure(s, FailKind::InvalidPropProperty(s.into())))?; @@ -400,6 +402,49 @@ fn parse_prop(s: &str) -> ParseResult { FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), )); } + } else if key == "rated" { + let mut it = num.splitn(2, ':'); + + let days: i32 = if let Ok(i) = it.next().unwrap().parse::() { + i + } else { + return Err(parse_failure( + s, + FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), + )); + } + + let ease = match it.next() { + Some(v) => { + let n: u8 = if let Ok(i) = v.parse() { + if (1..5).contains(i) { + EaseKind::AnswerButton(i) + } else { + return Err(parse_failure( + s, + FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), + )); + } + } else { + return Err(parse_failure( + s, + FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), + )); + } + } + None => EaseKind::AnyAnswerButton, + } + + PropertyKind::Rated(days, ease) + } else if key == "resched" { + if let Ok(days) = num.parse::() { + PropertyKind::Rated(days, EaseKind::ManualReschedule) + } else { + return Err(parse_failure( + s, + FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), + )); + } } else if let Ok(u) = num.parse::() { match prop { "ivl" => PropertyKind::Interval(u), diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 4e2faf076..dc61f9b79 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -272,6 +272,9 @@ impl SqlWriter<'_> { PropertyKind::Ease(ease) => { write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32) } + PropertyKind::Rated(days, ease) => { + write!(self.sql, "") + } } .unwrap(); Ok(()) diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index 94215697e..bc7ba5584 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -195,6 +195,10 @@ fn write_property(operator: &str, kind: &PropertyKind) -> String { Lapses(u) => format!("\"prop:lapses{}{}\"", operator, u), Ease(f) => format!("\"prop:ease{}{}\"", operator, f), Position(u) => format!("\"prop:pos{}{}\"", operator, u), + Rated(u, ease) => match ease { + Some(val) => format!("\"prop:rated{}{}:{}\"", operator, u, val), + None => format!("\"prop:rated{}{}\"", operator, u), + } } } From b57d0da12aeb61ebc941a79ca5b05acf83d19986 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sat, 9 Jan 2021 11:17:22 +0100 Subject: [PATCH 02/14] Implement functioning prop:rated --- rslib/src/search/sqlwriter.rs | 36 +++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index dc61f9b79..5481b2c67 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -116,6 +116,7 @@ impl SqlWriter<'_> { fn write_search_node_to_sql(&mut self, node: &SearchNode) -> Result<()> { use normalize_to_nfc as norm; + use std::cmp::max; match node { // note fields related SearchNode::UnqualifiedText(text) => self.write_unqualified(&self.norm_note(text)), @@ -144,7 +145,7 @@ impl SqlWriter<'_> { write!(self.sql, "c.did = {}", did).unwrap(); } SearchNode::NoteType(notetype) => self.write_note_type(&norm(notetype))?, - SearchNode::Rated { days, ease } => self.write_rated(*days, ease)?, + SearchNode::Rated { days, ease } => self.write_rated(*days, ease, "<")?, SearchNode::Tag(tag) => self.write_tag(&norm(tag))?, SearchNode::State(state) => self.write_state(state)?, @@ -214,9 +215,11 @@ impl SqlWriter<'_> { Ok(()) } - fn write_rated(&mut self, days: u32, ease: &EaseKind) -> Result<()> { + fn write_rated(&mut self, days: u32, ease: &EaseKind, op: &str) -> Result<()> { let today_cutoff = self.col.timing_today()?.next_day_at; let target_cutoff_ms = (today_cutoff - 86_400 * i64::from(days)) * 1_000; + let day_before_cutoff_ms = (today_cutoff - 86_400 * (days)) * 1_000; + write!( self.sql, "c.id in (select cid from revlog where id>{}", @@ -224,6 +227,17 @@ impl SqlWriter<'_> { ) .unwrap(); + // We use positive numbers for negative offsets + // which is why operators are reversed + match op { + "<" => write!(self.sql, "{} {}", ">", target_cutoff_ms), + ">" => write!(self.sql, "{} {}", "<", day_before_cutoff_ms), + "<=" => write!(self.sql, "{} {}", ">", day_before_cutoff_ms), + ">=" => write!(self.sql, "{} {}", "<", target_cutoff_ms), + "=" => write!(self.sql, "between {} and {}", target_cutoff_ms, day_before_cutoff_ms), + _ /* "!=" */ => write!(self.sql, "not between {} and {}", target_cutoff_ms, day_before_cutoff_ms), + }.unwrap(); + match ease { EaseKind::AnswerButton(u) => write!(self.sql, " and ease = {})", u), EaseKind::AnyAnswerButton => write!(self.sql, " and ease > 0)"), @@ -255,7 +269,7 @@ impl SqlWriter<'_> { previewrepeat = CardQueue::PreviewRepeat as i8, cutoff = timing.next_day_at, days = days - ) + ).unwrap() } PropertyKind::Position(pos) => { write!( @@ -264,19 +278,17 @@ impl SqlWriter<'_> { t = CardType::New as u8, op = op, pos = pos - ) + ).unwrap() } - PropertyKind::Interval(ivl) => write!(self.sql, "ivl {} {}", op, ivl), - PropertyKind::Reps(reps) => write!(self.sql, "reps {} {}", op, reps), - PropertyKind::Lapses(days) => write!(self.sql, "lapses {} {}", op, days), + PropertyKind::Interval(ivl) => write!(self.sql, "ivl {} {}", op, ivl).unwrap(), + PropertyKind::Reps(reps) => write!(self.sql, "reps {} {}", op, reps).unwrap(), + PropertyKind::Lapses(days) => write!(self.sql, "lapses {} {}", op, days).unwrap(), PropertyKind::Ease(ease) => { - write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32) - } - PropertyKind::Rated(days, ease) => { - write!(self.sql, "") + write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32).unwrap() } + PropertyKind::Rated(days, ease) => self.write_rated(*days, *ease, op)? } - .unwrap(); + Ok(()) } From 3788cb8890bef8250681c0b93e60bc0ba76c1fec Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 14 Jan 2021 17:58:21 +0100 Subject: [PATCH 03/14] Port prop:rated to EaseKind --- rslib/src/search/parser.rs | 13 +++++++++++-- rslib/src/search/sqlwriter.rs | 5 ++--- rslib/src/search/writer.rs | 5 +++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index d5ed46266..a6db30cba 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -87,7 +87,7 @@ pub enum PropertyKind { Lapses(u32), Ease(f32), Position(u32), - Rated(u32, Option), + Rated(u32, EaseKind), } #[derive(Debug, PartialEq, Clone)] @@ -433,7 +433,16 @@ fn parse_prop(s: &str) -> ParseResult { } } None => EaseKind::AnyAnswerButton, - } + }; + + PropertyKind::Rated(days, ease) + } else if key == "resched" { + let mut it = val.splitn(2, ':'); + + let n: u32 = it.next().unwrap().parse()?; + let days = n.max(1).min(365); + + let ease = EaseKind::ManualReschedule; PropertyKind::Rated(days, ease) } else if key == "resched" { diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 5481b2c67..60da1b8a4 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -116,7 +116,6 @@ impl SqlWriter<'_> { fn write_search_node_to_sql(&mut self, node: &SearchNode) -> Result<()> { use normalize_to_nfc as norm; - use std::cmp::max; match node { // note fields related SearchNode::UnqualifiedText(text) => self.write_unqualified(&self.norm_note(text)), @@ -218,7 +217,7 @@ impl SqlWriter<'_> { fn write_rated(&mut self, days: u32, ease: &EaseKind, op: &str) -> Result<()> { let today_cutoff = self.col.timing_today()?.next_day_at; let target_cutoff_ms = (today_cutoff - 86_400 * i64::from(days)) * 1_000; - let day_before_cutoff_ms = (today_cutoff - 86_400 * (days)) * 1_000; + let day_before_cutoff_ms = (today_cutoff - 86_400 * i64::from(days - 1)) * 1_000; write!( self.sql, @@ -286,7 +285,7 @@ impl SqlWriter<'_> { PropertyKind::Ease(ease) => { write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32).unwrap() } - PropertyKind::Rated(days, ease) => self.write_rated(*days, *ease, op)? + PropertyKind::Rated(days, ease) => self.write_rated(*days, ease, op)? } Ok(()) diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index bc7ba5584..41109e09b 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -196,8 +196,9 @@ fn write_property(operator: &str, kind: &PropertyKind) -> String { Ease(f) => format!("\"prop:ease{}{}\"", operator, f), Position(u) => format!("\"prop:pos{}{}\"", operator, u), Rated(u, ease) => match ease { - Some(val) => format!("\"prop:rated{}{}:{}\"", operator, u, val), - None => format!("\"prop:rated{}{}\"", operator, u), + EaseKind::AnswerButton(val) => format!("\"prop:rated{}{}:{}\"", operator, u, val), + EaseKind::AnyAnswerButton => format!("\"prop:rated{}{}\"", operator, u), + EaseKind::ManualReschedule => format!("\"prop:resched{}{}\"", operator, u), } } } From 88c75d73b695804a7813910a8c6f318d75067f03 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 14 Jan 2021 19:58:27 +0100 Subject: [PATCH 04/14] Pass in the the negative offset day to write_rated --- rslib/src/search/parser.rs | 6 +++--- rslib/src/search/sqlwriter.rs | 40 +++++++++++++++-------------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index a6db30cba..67365d7ec 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -87,7 +87,7 @@ pub enum PropertyKind { Lapses(u32), Ease(f32), Position(u32), - Rated(u32, EaseKind), + Rated(i32, EaseKind), } #[derive(Debug, PartialEq, Clone)] @@ -439,8 +439,8 @@ fn parse_prop(s: &str) -> ParseResult { } else if key == "resched" { let mut it = val.splitn(2, ':'); - let n: u32 = it.next().unwrap().parse()?; - let days = n.max(1).min(365); + let n: i32 = it.next().unwrap().parse()?; + let days = n.max(-365).min(0); let ease = EaseKind::ManualReschedule; diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 60da1b8a4..3c9997529 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -144,7 +144,7 @@ impl SqlWriter<'_> { write!(self.sql, "c.did = {}", did).unwrap(); } SearchNode::NoteType(notetype) => self.write_note_type(&norm(notetype))?, - SearchNode::Rated { days, ease } => self.write_rated(*days, ease, "<")?, + SearchNode::Rated { days, ease } => self.write_rated(-i64::from(*days), ease, ">")?, SearchNode::Tag(tag) => self.write_tag(&norm(tag))?, SearchNode::State(state) => self.write_state(state)?, @@ -214,28 +214,22 @@ impl SqlWriter<'_> { Ok(()) } - fn write_rated(&mut self, days: u32, ease: &EaseKind, op: &str) -> Result<()> { + fn write_rated(&mut self, days: i64, ease: &EaseKind, op: &str) -> Result<()> { let today_cutoff = self.col.timing_today()?.next_day_at; - let target_cutoff_ms = (today_cutoff - 86_400 * i64::from(days)) * 1_000; - let day_before_cutoff_ms = (today_cutoff - 86_400 * i64::from(days - 1)) * 1_000; + let target_cutoff_ms = (today_cutoff + 86_400 * days) * 1_000; + let day_before_cutoff_ms = (today_cutoff + 86_400 * (days - 1)) * 1_000; - write!( - self.sql, - "c.id in (select cid from revlog where id>{}", - target_cutoff_ms, - ) - .unwrap(); + write!(self.sql, "c.id in (select cid from revlog where id").unwrap(); - // We use positive numbers for negative offsets - // which is why operators are reversed match op { - "<" => write!(self.sql, "{} {}", ">", target_cutoff_ms), - ">" => write!(self.sql, "{} {}", "<", day_before_cutoff_ms), - "<=" => write!(self.sql, "{} {}", ">", day_before_cutoff_ms), - ">=" => write!(self.sql, "{} {}", "<", target_cutoff_ms), - "=" => write!(self.sql, "between {} and {}", target_cutoff_ms, day_before_cutoff_ms), - _ /* "!=" */ => write!(self.sql, "not between {} and {}", target_cutoff_ms, day_before_cutoff_ms), - }.unwrap(); + ">" => write!(self.sql, " {} {}", ">", target_cutoff_ms), + "<" => write!(self.sql, " {} {}", "<", day_before_cutoff_ms), + ">=" => write!(self.sql, " {} {}", ">", day_before_cutoff_ms), + "<=" => write!(self.sql, " {} {}", "<", target_cutoff_ms), + "=" => write!(self.sql, " between {} and {}", day_before_cutoff_ms, target_cutoff_ms), + _ /* "!=" */ => write!(self.sql, " not between {} and {}", day_before_cutoff_ms, target_cutoff_ms), + } + .unwrap(); match ease { EaseKind::AnswerButton(u) => write!(self.sql, " and ease = {})", u), @@ -285,7 +279,7 @@ impl SqlWriter<'_> { PropertyKind::Ease(ease) => { write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32).unwrap() } - PropertyKind::Rated(days, ease) => self.write_rated(*days, ease, op)? + PropertyKind::Rated(days, ease) => self.write_rated(i64::from(*days), ease, op)? } Ok(()) @@ -733,14 +727,14 @@ mod test { assert_eq!( s(ctx, "rated:2").0, format!( - "(c.id in (select cid from revlog where id>{} and ease > 0))", + "(c.id in (select cid from revlog where id > {} and ease > 0))", (timing.next_day_at - (86_400 * 2)) * 1_000 ) ); assert_eq!( s(ctx, "rated:400:1").0, format!( - "(c.id in (select cid from revlog where id>{} and ease = 1))", + "(c.id in (select cid from revlog where id > {} and ease = 1))", (timing.next_day_at - (86_400 * 365)) * 1_000 ) ); @@ -750,7 +744,7 @@ mod test { assert_eq!( s(ctx, "resched:400").0, format!( - "(c.id in (select cid from revlog where id>{} and ease = 0))", + "(c.id in (select cid from revlog where id > {} and ease = 0))", (timing.next_day_at - (86_400 * 365)) * 1_000 ) ); From 8f3c63bf0bf2b55fde3dc83e049a35b79871d41b Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 14 Jan 2021 21:01:40 +0100 Subject: [PATCH 05/14] Fix formatting --- rslib/src/search/sqlwriter.rs | 19 +++++++++---------- rslib/src/search/writer.rs | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 3c9997529..ad326d49d 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -264,22 +264,21 @@ impl SqlWriter<'_> { days = days ).unwrap() } - PropertyKind::Position(pos) => { - write!( - self.sql, - "(c.type = {t} and due {op} {pos})", - t = CardType::New as u8, - op = op, - pos = pos - ).unwrap() - } + PropertyKind::Position(pos) => write!( + self.sql, + "(c.type = {t} and due {op} {pos})", + t = CardType::New as u8, + op = op, + pos = pos + ) + .unwrap(), PropertyKind::Interval(ivl) => write!(self.sql, "ivl {} {}", op, ivl).unwrap(), PropertyKind::Reps(reps) => write!(self.sql, "reps {} {}", op, reps).unwrap(), PropertyKind::Lapses(days) => write!(self.sql, "lapses {} {}", op, days).unwrap(), PropertyKind::Ease(ease) => { write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32).unwrap() } - PropertyKind::Rated(days, ease) => self.write_rated(i64::from(*days), ease, op)? + PropertyKind::Rated(days, ease) => self.write_rated(i64::from(*days), ease, op)?, } Ok(()) diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index 41109e09b..1715c11e3 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -199,7 +199,7 @@ fn write_property(operator: &str, kind: &PropertyKind) -> String { EaseKind::AnswerButton(val) => format!("\"prop:rated{}{}:{}\"", operator, u, val), EaseKind::AnyAnswerButton => format!("\"prop:rated{}{}\"", operator, u), EaseKind::ManualReschedule => format!("\"prop:resched{}{}\"", operator, u), - } + }, } } From 908e0a375c53b8674765c0521a7ec71b65a7b925 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 14 Jan 2021 21:22:30 +0100 Subject: [PATCH 06/14] Being overly correct with the review ids --- rslib/src/search/sqlwriter.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index ad326d49d..08363581b 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -222,12 +222,12 @@ impl SqlWriter<'_> { write!(self.sql, "c.id in (select cid from revlog where id").unwrap(); match op { - ">" => write!(self.sql, " {} {}", ">", target_cutoff_ms), - "<" => write!(self.sql, " {} {}", "<", day_before_cutoff_ms), - ">=" => write!(self.sql, " {} {}", ">", day_before_cutoff_ms), - "<=" => write!(self.sql, " {} {}", "<", target_cutoff_ms), - "=" => write!(self.sql, " between {} and {}", day_before_cutoff_ms, target_cutoff_ms), - _ /* "!=" */ => write!(self.sql, " not between {} and {}", day_before_cutoff_ms, target_cutoff_ms), + ">" => write!(self.sql, " >= {}", target_cutoff_ms), + ">=" => write!(self.sql, " >= {}", day_before_cutoff_ms), + "<" => write!(self.sql, " < {}", day_before_cutoff_ms), + "<=" => write!(self.sql, " < {}", target_cutoff_ms), + "=" => write!(self.sql, " between {} and {}", day_before_cutoff_ms, target_cutoff_ms - 1), + _ /* "!=" */ => write!(self.sql, " not between {} and {}", day_before_cutoff_ms, target_cutoff_ms - 1), } .unwrap(); @@ -726,14 +726,14 @@ mod test { assert_eq!( s(ctx, "rated:2").0, format!( - "(c.id in (select cid from revlog where id > {} and ease > 0))", + "(c.id in (select cid from revlog where id >= {} and ease > 0))", (timing.next_day_at - (86_400 * 2)) * 1_000 ) ); assert_eq!( s(ctx, "rated:400:1").0, format!( - "(c.id in (select cid from revlog where id > {} and ease = 1))", + "(c.id in (select cid from revlog where id >= {} and ease = 1))", (timing.next_day_at - (86_400 * 365)) * 1_000 ) ); @@ -743,7 +743,7 @@ mod test { assert_eq!( s(ctx, "resched:400").0, format!( - "(c.id in (select cid from revlog where id > {} and ease = 0))", + "(c.id in (select cid from revlog where id >= {} and ease = 0))", (timing.next_day_at - (86_400 * 365)) * 1_000 ) ); From 47542f97e5fd5e87cdf1493a990103642453945f Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sat, 16 Jan 2021 13:07:35 +0100 Subject: [PATCH 07/14] Fix issues after rebasing --- rslib/src/search/parser.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 67365d7ec..f5a3554a8 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -402,7 +402,7 @@ fn parse_prop(s: &str) -> ParseResult { FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), )); } - } else if key == "rated" { + } else if prop == "rated" { let mut it = num.splitn(2, ':'); let days: i32 = if let Ok(i) = it.next().unwrap().parse::() { @@ -412,13 +412,13 @@ fn parse_prop(s: &str) -> ParseResult { s, FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), )); - } + }; let ease = match it.next() { Some(v) => { - let n: u8 = if let Ok(i) = v.parse() { - if (1..5).contains(i) { - EaseKind::AnswerButton(i) + if let Ok(u) = v.parse::() { + if (1..5).contains(&u) { + EaseKind::AnswerButton(u) } else { return Err(parse_failure( s, @@ -436,16 +436,7 @@ fn parse_prop(s: &str) -> ParseResult { }; PropertyKind::Rated(days, ease) - } else if key == "resched" { - let mut it = val.splitn(2, ':'); - - let n: i32 = it.next().unwrap().parse()?; - let days = n.max(-365).min(0); - - let ease = EaseKind::ManualReschedule; - - PropertyKind::Rated(days, ease) - } else if key == "resched" { + } else if prop == "resched" { if let Ok(days) = num.parse::() { PropertyKind::Rated(days, EaseKind::ManualReschedule) } else { From 112e7f577ed6d66c432197622de0b410fad3101a Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sat, 16 Jan 2021 15:08:15 +0100 Subject: [PATCH 08/14] Lift the 365 limit from rated/resched searches --- rslib/src/search/parser.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index f5a3554a8..f0091e80b 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -351,9 +351,9 @@ fn parse_flag(s: &str) -> ParseResult { /// eg resched:3 fn parse_resched(s: &str) -> ParseResult { - if let Ok(d) = s.parse::() { + if let Ok(days) = s.parse::() { Ok(SearchNode::Rated { - days: d.max(1).min(365), + days, ease: EaseKind::ManualReschedule, }) } else { @@ -488,8 +488,7 @@ fn parse_edited(s: &str) -> ParseResult { /// second arg must be between 1-4 fn parse_rated(s: &str) -> ParseResult { let mut it = s.splitn(2, ':'); - if let Ok(d) = it.next().unwrap().parse::() { - let days = d.max(1).min(365); + if let Ok(days) = it.next().unwrap().parse::() { let ease = if let Some(tail) = it.next() { if let Ok(u) = tail.parse::() { if u > 0 && u < 5 { From e7660113ced30ccaa629c6b49132b4132ed72e30 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sat, 16 Jan 2021 15:24:22 +0100 Subject: [PATCH 09/14] Change argument order of write_rated to be more in line with the logic --- rslib/src/search/sqlwriter.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 08363581b..9b0fe3205 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -144,7 +144,7 @@ impl SqlWriter<'_> { write!(self.sql, "c.did = {}", did).unwrap(); } SearchNode::NoteType(notetype) => self.write_note_type(&norm(notetype))?, - SearchNode::Rated { days, ease } => self.write_rated(-i64::from(*days), ease, ">")?, + SearchNode::Rated { days, ease } => self.write_rated(">", -i64::from(*days), ease)?, SearchNode::Tag(tag) => self.write_tag(&norm(tag))?, SearchNode::State(state) => self.write_state(state)?, @@ -214,7 +214,7 @@ impl SqlWriter<'_> { Ok(()) } - fn write_rated(&mut self, days: i64, ease: &EaseKind, op: &str) -> Result<()> { + fn write_rated(&mut self, op: &str, days: i64, ease: &EaseKind) -> Result<()> { let today_cutoff = self.col.timing_today()?.next_day_at; let target_cutoff_ms = (today_cutoff + 86_400 * days) * 1_000; let day_before_cutoff_ms = (today_cutoff + 86_400 * (days - 1)) * 1_000; @@ -278,7 +278,7 @@ impl SqlWriter<'_> { PropertyKind::Ease(ease) => { write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32).unwrap() } - PropertyKind::Rated(days, ease) => self.write_rated(i64::from(*days), ease, op)?, + PropertyKind::Rated(days, ease) => self.write_rated(op, i64::from(*days), ease)?, } Ok(()) From 947260e4aaebf77dcaf40b09b501a0828c8f7e8e Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sat, 16 Jan 2021 17:38:02 +0100 Subject: [PATCH 10/14] Reintroduce false removed limits --- rslib/src/search/parser.rs | 5 +++-- rslib/src/search/sqlwriter.rs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index f0091e80b..59710b5a9 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -406,7 +406,7 @@ fn parse_prop(s: &str) -> ParseResult { let mut it = num.splitn(2, ':'); let days: i32 = if let Ok(i) = it.next().unwrap().parse::() { - i + i.min(0) } else { return Err(parse_failure( s, @@ -438,7 +438,7 @@ fn parse_prop(s: &str) -> ParseResult { PropertyKind::Rated(days, ease) } else if prop == "resched" { if let Ok(days) = num.parse::() { - PropertyKind::Rated(days, EaseKind::ManualReschedule) + PropertyKind::Rated(days.min(0), EaseKind::ManualReschedule) } else { return Err(parse_failure( s, @@ -489,6 +489,7 @@ fn parse_edited(s: &str) -> ParseResult { fn parse_rated(s: &str) -> ParseResult { let mut it = s.splitn(2, ':'); if let Ok(days) = it.next().unwrap().parse::() { + let days = days.max(1); let ease = if let Some(tail) = it.next() { if let Ok(u) = tail.parse::() { if u > 0 && u < 5 { diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 9b0fe3205..7a359333a 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -734,7 +734,7 @@ mod test { s(ctx, "rated:400:1").0, format!( "(c.id in (select cid from revlog where id >= {} and ease = 1))", - (timing.next_day_at - (86_400 * 365)) * 1_000 + (timing.next_day_at - (86_400 * 400)) * 1_000 ) ); assert_eq!(s(ctx, "rated:0").0, s(ctx, "rated:1").0); @@ -744,7 +744,7 @@ mod test { s(ctx, "resched:400").0, format!( "(c.id in (select cid from revlog where id >= {} and ease = 0))", - (timing.next_day_at - (86_400 * 365)) * 1_000 + (timing.next_day_at - (86_400 * 400)) * 1_000 ) ); @@ -759,6 +759,7 @@ mod test { cutoff = timing.next_day_at ) ); + assert_eq!(s(ctx, "prop:rated>-5:3").0, s(ctx, "rated:5:3").0); // note types by name assert_eq!( From bc81165be4edc36adefbe32a161a91eb8689487e Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sun, 17 Jan 2021 21:44:56 +0100 Subject: [PATCH 11/14] Add resched to tags --- rslib/src/search/parser.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 59710b5a9..a491a4694 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -371,6 +371,7 @@ fn parse_prop(s: &str) -> ParseResult { tag("ease"), tag("pos"), tag("rated"), + tag("resched"), ))(s) .map_err(|_| parse_failure(s, FailKind::InvalidPropProperty(s.into())))?; @@ -460,10 +461,7 @@ fn parse_prop(s: &str) -> ParseResult { )); }; - Ok(SearchNode::Property { - operator: operator.to_string(), - kind, - }) + Ok(SearchNode::Property { operator, kind }) } /// eg added:1 From 2b45ef22a5b536fcf6d8814a88a69b2ce7fce342 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sun, 17 Jan 2021 21:56:25 +0100 Subject: [PATCH 12/14] Use explicit unreachable in rust pattern matching --- rslib/src/search/parser.rs | 5 ++++- rslib/src/search/sqlwriter.rs | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index a491a4694..e547f319b 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -461,7 +461,10 @@ fn parse_prop(s: &str) -> ParseResult { )); }; - Ok(SearchNode::Property { operator, kind }) + Ok(SearchNode::Property { + operator: operator.to_string(), + kind, + }) } /// eg added:1 diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 7a359333a..e64b7f6df 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -226,8 +226,19 @@ impl SqlWriter<'_> { ">=" => write!(self.sql, " >= {}", day_before_cutoff_ms), "<" => write!(self.sql, " < {}", day_before_cutoff_ms), "<=" => write!(self.sql, " < {}", target_cutoff_ms), - "=" => write!(self.sql, " between {} and {}", day_before_cutoff_ms, target_cutoff_ms - 1), - _ /* "!=" */ => write!(self.sql, " not between {} and {}", day_before_cutoff_ms, target_cutoff_ms - 1), + "=" => write!( + self.sql, + " between {} and {}", + day_before_cutoff_ms, + target_cutoff_ms - 1 + ), + "!=" => write!( + self.sql, + " not between {} and {}", + day_before_cutoff_ms, + target_cutoff_ms - 1 + ), + _ => unreachable!("unexpected op"), } .unwrap(); From bdc6494c7980cf83c99f51f6ae3d0177f7e378eb Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sun, 17 Jan 2021 23:55:05 +0100 Subject: [PATCH 13/14] Generalize InvalidRatedEase error for rated and prop:rated --- ftl/core/search.ftl | 2 +- rslib/src/search/parser.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ftl/core/search.ftl b/ftl/core/search.ftl index 1869741bc..fcae80877 100644 --- a/ftl/core/search.ftl +++ b/ftl/core/search.ftl @@ -17,7 +17,7 @@ search-invalid-argument = `{ $term }` was given an invalid argument '`{ $argumen search-invalid-flag = `flag:` must be followed by a valid flag number: `1` (red), `2` (orange), `3` (green), `4` (blue) or `0` (no flag). search-invalid-followed-by-positive-days = `{ $term }` must be followed by a positive number of days. search-invalid-rated-days = `rated:` must be followed by a positive number of days. -search-invalid-rated-ease = `rated:{ $val }:` must be followed by `1` (again), `2` (hard), `3` (good) or `4` (easy). +search-invalid-rated-ease = `{ $val }:` must be followed by `1` (again), `2` (hard), `3` (good) or `4` (easy). search-invalid-prop-operator = `prop:{ $val }` must be followed by one of the comparison operators: `=`, `!=`, `<`, `>`, `<=` or `>=`. search-invalid-prop-float = `prop:{ $val }` must be followed by a decimal number. search-invalid-prop-integer = `prop:{ $val }` must be followed by a whole number. diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index e547f319b..99e25bcd1 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -423,7 +423,7 @@ fn parse_prop(s: &str) -> ParseResult { } else { return Err(parse_failure( s, - FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), + FailKind::InvalidRatedEase(format!("prop:{}{}{}", prop, operator, days.to_string())), )); } } else { @@ -498,13 +498,13 @@ fn parse_rated(s: &str) -> ParseResult { } else { return Err(parse_failure( s, - FailKind::InvalidRatedEase(days.to_string()), + FailKind::InvalidRatedEase(format!("rated:{}", days.to_string())), )); } } else { return Err(parse_failure( s, - FailKind::InvalidRatedEase(days.to_string()), + FailKind::InvalidRatedEase(format!("rated:{}", days.to_string())), )); } } else { From 84c997fa4de33b3d6a94267d3503e8ddca44012d Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 18 Jan 2021 00:05:05 +0100 Subject: [PATCH 14/14] Adjust unit tests for new InvalidRatedEase --- rslib/src/search/parser.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 99e25bcd1..6fdff4d8b 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -423,7 +423,12 @@ fn parse_prop(s: &str) -> ParseResult { } else { return Err(parse_failure( s, - FailKind::InvalidRatedEase(format!("prop:{}{}{}", prop, operator, days.to_string())), + FailKind::InvalidRatedEase(format!( + "prop:{}{}{}", + prop, + operator, + days.to_string() + )), )); } } else { @@ -918,10 +923,10 @@ mod test { assert_err_kind("rated:", InvalidRatedDays); assert_err_kind("rated:foo", InvalidRatedDays); - assert_err_kind("rated:1:", InvalidRatedEase("1".to_string())); - assert_err_kind("rated:2:-1", InvalidRatedEase("2".to_string())); - assert_err_kind("rated:3:1.1", InvalidRatedEase("3".to_string())); - assert_err_kind("rated:0:foo", InvalidRatedEase("1".to_string())); + assert_err_kind("rated:1:", InvalidRatedEase("rated:1".to_string())); + assert_err_kind("rated:2:-1", InvalidRatedEase("rated:2".to_string())); + assert_err_kind("rated:3:1.1", InvalidRatedEase("rated:3".to_string())); + assert_err_kind("rated:0:foo", InvalidRatedEase("rated:1".to_string())); assert_err_kind("resched:", FailKind::InvalidResched); assert_err_kind("resched:-1", FailKind::InvalidResched);