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,
ease: Option<u8>,
},
Tag(OptionalRe<'a>),
Tag(String),
Duplicates {
note_type_id: NoteTypeID,
text: Cow<'a, str>,
@ -294,18 +294,18 @@ fn search_node_for_text_with_argument<'a>(
"prop" => parse_prop(val)?,
"re" => SearchNode::Regex(unescape_quotes(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
_ => parse_single_field(key, val)?,
})
}
/// 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) {
Err(ParseError {})
} 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.
/// Return error if there is an undefined escape sequence.
fn unescape_to_enforced_re(txt: &str) -> ParseResult<String> {
Ok(match unescape_to_re(txt)? {
fn unescape_to_enforced_re(txt: &str, wildcard: &str) -> ParseResult<String> {
Ok(match unescape_to_custom_re(txt, wildcard)? {
OptionalRe::Text(s) => regex::escape(s.as_ref()),
OptionalRe::Re(s) => s.to_string(),
})
@ -643,7 +643,7 @@ mod test {
parse("note:basic")?,
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!(
parse("nid:1237123712,2,3")?,
vec![Search(NoteIDs("1237123712,2,3".into()))]

View File

@ -188,28 +188,16 @@ impl SqlWriter<'_> {
.unwrap();
}
fn write_tag(&mut self, text: &OptionalRe) -> Result<()> {
match text {
OptionalRe::Text(s) => {
if s == "none" {
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();
self.args.push(re);
}
fn write_tag(&mut self, s: &String) -> Result<()> {
match s.as_str() {
"none" => write!(self.sql, "n.tags = ''").unwrap(),
r"\S*" => write!(self.sql, "true").unwrap(),
_ => {
write!(self.sql, "n.tags regexp ?").unwrap();
self.args.push(format!("(?i).* {} .*", s));
}
}
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
ctx.transact(None, |col| col.register_tag("One", Usn(-1)))
.unwrap();
assert_eq!(
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