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:
parent
d1ee507b3a
commit
57787368a1
@ -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()))]
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user