fix escaping of * in field search

mentioned in
https://github.com/ankitects/anki/pull/769
This commit is contained in:
Damien Elmes 2020-09-27 16:00:21 +10:00
parent d9562a1898
commit 7e872de875

View file

@ -13,6 +13,8 @@ use crate::{
text::{normalize_to_nfc, strip_html_preserving_image_filenames, without_combining}, text::{normalize_to_nfc, strip_html_preserving_image_filenames, without_combining},
timestamp::TimestampSecs, timestamp::TimestampSecs,
}; };
use lazy_static::lazy_static;
use regex::Regex;
use std::{borrow::Cow, fmt::Write}; use std::{borrow::Cow, fmt::Write};
pub(crate) struct SqlWriter<'a> { pub(crate) struct SqlWriter<'a> {
@ -391,12 +393,15 @@ impl SqlWriter<'_> {
} }
let cmp; let cmp;
let cmp_trailer;
if is_re { if is_re {
cmp = "regexp"; cmp = "regexp";
cmp_trailer = "";
self.args.push(format!("(?i){}", val)); self.args.push(format!("(?i){}", val));
} else { } else {
cmp = "like"; cmp = "like";
self.args.push(val.replace('*', "%")); cmp_trailer = "escape '\\'";
self.args.push(convert_glob_char(val).into())
} }
let arg_idx = self.args.len(); let arg_idx = self.args.len();
@ -404,10 +409,11 @@ impl SqlWriter<'_> {
.iter() .iter()
.map(|(ntid, ord)| { .map(|(ntid, ord)| {
format!( format!(
"(n.mid = {mid} and field_at_index(n.flds, {ord}) {cmp} ?{n})", "(n.mid = {mid} and field_at_index(n.flds, {ord}) {cmp} ?{n} {cmp_trailer})",
mid = ntid, mid = ntid,
ord = ord.unwrap_or_default(), ord = ord.unwrap_or_default(),
cmp = cmp, cmp = cmp,
cmp_trailer = cmp_trailer,
n = arg_idx n = arg_idx
) )
}) })
@ -455,6 +461,14 @@ impl SqlWriter<'_> {
} }
} }
/// Replace * with %, leaving \* alone.
fn convert_glob_char(val: &str) -> Cow<str> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(^|[^\\])\*").unwrap();
}
RE.replace_all(val, "${1}%")
}
/// Convert a string with _, % or * characters into a regex. /// Convert a string with _, % or * characters into a regex.
/// If string contains no globbing characters, return None. /// If string contains no globbing characters, return None.
fn glob_to_re(glob: &str) -> Option<String> { fn glob_to_re(glob: &str) -> Option<String> {
@ -596,10 +610,10 @@ mod test {
s(ctx, "front:te*st"), s(ctx, "front:te*st"),
( (
concat!( concat!(
"(((n.mid = 1581236385344 and field_at_index(n.flds, 0) like ?1) or ", "(((n.mid = 1581236385344 and field_at_index(n.flds, 0) like ?1 escape '\\') or ",
"(n.mid = 1581236385345 and field_at_index(n.flds, 0) like ?1) or ", "(n.mid = 1581236385345 and field_at_index(n.flds, 0) like ?1 escape '\\') or ",
"(n.mid = 1581236385346 and field_at_index(n.flds, 0) like ?1) or ", "(n.mid = 1581236385346 and field_at_index(n.flds, 0) like ?1 escape '\\') or ",
"(n.mid = 1581236385347 and field_at_index(n.flds, 0) like ?1)))" "(n.mid = 1581236385347 and field_at_index(n.flds, 0) like ?1 escape '\\')))"
) )
.into(), .into(),
vec!["te%st".into()] vec!["te%st".into()]
@ -788,4 +802,12 @@ mod test {
RequiredTable::Notes RequiredTable::Notes
); );
} }
#[test]
fn convert_glob() {
assert_eq!(&convert_glob_char("foo*bar"), "foo%bar");
assert_eq!(&convert_glob_char("*bar"), "%bar");
assert_eq!(&convert_glob_char("\n*bar"), "\n%bar");
assert_eq!(&convert_glob_char(r"\*bar"), r"\*bar");
}
} }