Always use regex for tag search

Don't distinguish between the glob and no-glob cases when comparing
tags but always use regexp. Thus, avoid problems with SQL wildcards in
registered tags.
This commit is contained in:
RumovZ 2020-11-15 09:52:00 +01:00
parent d1ee507b3a
commit 57787368a1
2 changed files with 16 additions and 31 deletions

View File

@ -76,7 +76,7 @@ pub(super) enum SearchNode<'a> {
days: u32, days: u32,
ease: Option<u8>, ease: Option<u8>,
}, },
Tag(OptionalRe<'a>), Tag(String),
Duplicates { Duplicates {
note_type_id: NoteTypeID, note_type_id: NoteTypeID,
text: Cow<'a, str>, text: Cow<'a, str>,
@ -294,18 +294,18 @@ fn search_node_for_text_with_argument<'a>(
"prop" => parse_prop(val)?, "prop" => parse_prop(val)?,
"re" => SearchNode::Regex(unescape_quotes(val)), "re" => SearchNode::Regex(unescape_quotes(val)),
"nc" => SearchNode::NoCombining(unescape_to_glob(val)?), "nc" => SearchNode::NoCombining(unescape_to_glob(val)?),
"w" => SearchNode::WordBoundary(unescape_to_enforced_re(val)?), "w" => SearchNode::WordBoundary(unescape_to_enforced_re(val, ".")?),
// anything else is a field search // anything else is a field search
_ => parse_single_field(key, val)?, _ => parse_single_field(key, val)?,
}) })
} }
/// Ensure the string doesn't contain whitespace and unescape. /// Ensure the string doesn't contain whitespace and unescape.
fn parse_tag(s: &str) -> ParseResult<OptionalRe> { fn parse_tag(s: &str) -> ParseResult<String> {
if s.as_bytes().iter().any(u8::is_ascii_whitespace) { if s.as_bytes().iter().any(u8::is_ascii_whitespace) {
Err(ParseError {}) Err(ParseError {})
} else { } else {
unescape_to_custom_re(s, r"\S") unescape_to_enforced_re(s, r"\S")
} }
} }
@ -527,8 +527,8 @@ fn unescape_to_custom_re<'a>(txt: &'a str, wildcard: &str) -> ParseResult<Option
/// Handle escaped characters and convert to regex. /// Handle escaped characters and convert to regex.
/// Return error if there is an undefined escape sequence. /// Return error if there is an undefined escape sequence.
fn unescape_to_enforced_re(txt: &str) -> ParseResult<String> { fn unescape_to_enforced_re(txt: &str, wildcard: &str) -> ParseResult<String> {
Ok(match unescape_to_re(txt)? { Ok(match unescape_to_custom_re(txt, wildcard)? {
OptionalRe::Text(s) => regex::escape(s.as_ref()), OptionalRe::Text(s) => regex::escape(s.as_ref()),
OptionalRe::Re(s) => s.to_string(), OptionalRe::Re(s) => s.to_string(),
}) })
@ -643,7 +643,7 @@ mod test {
parse("note:basic")?, parse("note:basic")?,
vec![Search(NoteType(Text("basic".into())))] vec![Search(NoteType(Text("basic".into())))]
); );
assert_eq!(parse("tag:hard")?, vec![Search(Tag(Text("hard".into())))]); assert_eq!(parse("tag:hard")?, vec![Search(Tag("hard".to_string()))]);
assert_eq!( assert_eq!(
parse("nid:1237123712,2,3")?, parse("nid:1237123712,2,3")?,
vec![Search(NoteIDs("1237123712,2,3".into()))] vec![Search(NoteIDs("1237123712,2,3".into()))]

View File

@ -188,28 +188,16 @@ impl SqlWriter<'_> {
.unwrap(); .unwrap();
} }
fn write_tag(&mut self, text: &OptionalRe) -> Result<()> { fn write_tag(&mut self, s: &String) -> Result<()> {
match text { match s.as_str() {
OptionalRe::Text(s) => { "none" => write!(self.sql, "n.tags = ''").unwrap(),
if s == "none" { r"\S*" => write!(self.sql, "true").unwrap(),
write!(self.sql, "n.tags = ''").unwrap(); _ => {
} else if let Some(tag) = self.col.storage.preferred_tag_case(s)? {
write!(self.sql, "n.tags like ?").unwrap();
self.args.push(format!("% {} %", tag));
} else {
write!(self.sql, "false").unwrap();
}
}
OptionalRe::Re(s) => {
if s == r"\S*" {
write!(self.sql, "true").unwrap();
} else {
let re = format!("(?i).* {} .*", s);
write!(self.sql, "n.tags regexp ?").unwrap(); write!(self.sql, "n.tags regexp ?").unwrap();
self.args.push(re); self.args.push(format!("(?i).* {} .*", s));
}
} }
} }
Ok(()) Ok(())
} }
@ -668,15 +656,12 @@ mod test {
) )
); );
// unregistered tag short circuits
assert_eq!(s(ctx, r"tag:one"), ("(false)".into(), vec![]));
// if registered, searches with canonical // if registered, searches with canonical
ctx.transact(None, |col| col.register_tag("One", Usn(-1))) ctx.transact(None, |col| col.register_tag("One", Usn(-1)))
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
s(ctx, r"tag:one"), s(ctx, r"tag:one"),
("(n.tags like ?)".into(), vec![r"% One %".into()]) ("(n.tags regexp ?)".into(), vec![r"(?i).* one .*".into()])
); );
// wildcards force a regexp search // wildcards force a regexp search