diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index f59378be4..46575350c 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -53,6 +53,7 @@ pub(super) enum SearchNode<'a> { SingleField { field: Cow<'a, str>, text: Cow<'a, str>, + is_re: bool, }, AddedInDays(u32), CardTemplate(TemplateKind), @@ -272,10 +273,7 @@ fn search_node_for_text_with_argument<'a>( "prop" => parse_prop(val.as_ref())?, "re" => SearchNode::Regex(val), // anything else is a field search - _ => SearchNode::SingleField { - field: key, - text: val, - }, + _ => parse_single_field(key.as_ref(), val.as_ref()), }) } @@ -392,6 +390,20 @@ fn parse_template(val: &str) -> SearchNode<'static> { }) } +fn parse_single_field(key: &str, mut val: &str) -> SearchNode<'static> { + let is_re = if val.starts_with("re:") { + val = val.trim_start_matches("re:"); + true + } else { + false + }; + SearchNode::SingleField { + field: key.to_string().into(), + text: val.to_string().into(), + is_re, + } +} + #[cfg(test)] mod test { use super::*; @@ -424,7 +436,8 @@ mod test { And, Search(SingleField { field: "foo".into(), - text: "bar baz".into() + text: "bar baz".into(), + is_re: false, }) ]))), Or, @@ -432,6 +445,15 @@ mod test { ] ); + assert_eq!( + parse("foo:re:bar")?, + vec![Search(SingleField { + field: "foo".into(), + text: "bar".into(), + is_re: true + })] + ); + // any character should be escapable in quotes assert_eq!( parse(r#""re:\btest""#)?, diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index d259d88a6..dee776f45 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -55,8 +55,8 @@ impl SqlWriter<'_, '_> { fn write_search_node_to_sql(&mut self, node: &SearchNode) -> Result<()> { match node { SearchNode::UnqualifiedText(text) => self.write_unqualified(text), - SearchNode::SingleField { field, text } => { - self.write_single_field(field.as_ref(), text.as_ref())? + SearchNode::SingleField { field, text, is_re } => { + self.write_single_field(field.as_ref(), text.as_ref(), *is_re)? } SearchNode::AddedInDays(days) => self.write_added(*days)?, SearchNode::CardTemplate(template) => self.write_template(template)?, @@ -279,7 +279,7 @@ impl SqlWriter<'_, '_> { Ok(()) } - fn write_single_field(&mut self, field_name: &str, val: &str) -> Result<()> { + fn write_single_field(&mut self, field_name: &str, val: &str, is_re: bool) -> Result<()> { let note_types = self.req.storage.all_note_types()?; let mut field_map = vec![]; @@ -299,15 +299,24 @@ impl SqlWriter<'_, '_> { return Ok(()); } - self.args.push(val.replace('*', "%")); + let cmp; + if is_re { + cmp = "regexp"; + self.args.push(format!("(?i){}", val)); + } else { + cmp = "like"; + self.args.push(val.replace('*', "%")); + } + let arg_idx = self.args.len(); let searches: Vec<_> = field_map .iter() .map(|(ntid, ord)| { format!( - "(n.mid = {mid} and field_at_index(n.flds, {ord}) like ?{n})", + "(n.mid = {mid} and field_at_index(n.flds, {ord}) {cmp} ?{n})", mid = ntid, ord = ord, + cmp = cmp, n = arg_idx ) })