mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 22:42:25 -04:00
handle fields with embedded nuls
This commit is contained in:
parent
5c5d1c2af5
commit
289bdde20c
2 changed files with 65 additions and 45 deletions
|
@ -40,11 +40,16 @@ serde_repr = "0.1.5"
|
||||||
num_enum = "0.4.2"
|
num_enum = "0.4.2"
|
||||||
unicase = "2.6.0"
|
unicase = "2.6.0"
|
||||||
|
|
||||||
[target.'cfg(target_vendor="apple")'.dependencies]
|
# pinned until rusqlite 0.22 comes out
|
||||||
rusqlite = { version = "0.21.0", features = ["trace", "functions", "collation"] }
|
[target.'cfg(target_vendor="apple")'.dependencies.rusqlite]
|
||||||
|
git = "https://github.com/ankitects/rusqlite.git"
|
||||||
|
branch="nulsafe-text"
|
||||||
|
features = ["trace", "functions", "collation"]
|
||||||
|
|
||||||
[target.'cfg(not(target_vendor="apple"))'.dependencies]
|
[target.'cfg(not(target_vendor="apple"))'.dependencies.rusqlite]
|
||||||
rusqlite = { version = "0.21.0", features = ["trace", "functions", "collation", "bundled"] }
|
git = "https://github.com/ankitects/rusqlite.git"
|
||||||
|
branch="nulsafe-text"
|
||||||
|
features = ["trace", "functions", "collation", "bundled"]
|
||||||
|
|
||||||
[target.'cfg(linux)'.dependencies]
|
[target.'cfg(linux)'.dependencies]
|
||||||
reqwest = { version = "0.10.1", features = ["json", "native-tls-vendored"] }
|
reqwest = { version = "0.10.1", features = ["json", "native-tls-vendored"] }
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
types::{ObjID, Usn},
|
types::{ObjID, Usn},
|
||||||
};
|
};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rusqlite::{params, Connection, NO_PARAMS};
|
use rusqlite::{functions::FunctionFlags, params, Connection, NO_PARAMS};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
@ -71,58 +71,73 @@ fn open_or_create_collection_db(path: &Path) -> Result<Connection> {
|
||||||
/// to split provided fields and return field at zero-based index.
|
/// to split provided fields and return field at zero-based index.
|
||||||
/// If out of range, returns empty string.
|
/// If out of range, returns empty string.
|
||||||
fn add_field_index_function(db: &Connection) -> rusqlite::Result<()> {
|
fn add_field_index_function(db: &Connection) -> rusqlite::Result<()> {
|
||||||
db.create_scalar_function("field_at_index", 2, true, |ctx| {
|
db.create_scalar_function(
|
||||||
let mut fields = ctx.get_raw(0).as_str()?.split('\x1f');
|
"field_at_index",
|
||||||
let idx: u16 = ctx.get(1)?;
|
2,
|
||||||
Ok(fields.nth(idx as usize).unwrap_or("").to_string())
|
FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
})
|
|ctx| {
|
||||||
|
let mut fields = ctx.get_raw(0).as_str()?.split('\x1f');
|
||||||
|
let idx: u16 = ctx.get(1)?;
|
||||||
|
Ok(fields.nth(idx as usize).unwrap_or("").to_string())
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_without_combining_function(db: &Connection) -> rusqlite::Result<()> {
|
fn add_without_combining_function(db: &Connection) -> rusqlite::Result<()> {
|
||||||
db.create_scalar_function("without_combining", 1, true, |ctx| {
|
db.create_scalar_function(
|
||||||
let text = ctx.get_raw(0).as_str()?;
|
"without_combining",
|
||||||
Ok(match without_combining(text) {
|
1,
|
||||||
Cow::Borrowed(_) => None,
|
FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
Cow::Owned(o) => Some(o),
|
|ctx| {
|
||||||
})
|
let text = ctx.get_raw(0).as_str()?;
|
||||||
})
|
Ok(match without_combining(text) {
|
||||||
|
Cow::Borrowed(_) => None,
|
||||||
|
Cow::Owned(o) => Some(o),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds sql function regexp(regex, string) -> is_match
|
/// Adds sql function regexp(regex, string) -> is_match
|
||||||
/// Taken from the rusqlite docs
|
/// Taken from the rusqlite docs
|
||||||
fn add_regexp_function(db: &Connection) -> rusqlite::Result<()> {
|
fn add_regexp_function(db: &Connection) -> rusqlite::Result<()> {
|
||||||
db.create_scalar_function("regexp", 2, true, move |ctx| {
|
db.create_scalar_function(
|
||||||
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
"regexp",
|
||||||
|
2,
|
||||||
|
FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
|
move |ctx| {
|
||||||
|
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
||||||
|
|
||||||
let saved_re: Option<&Regex> = ctx.get_aux(0)?;
|
let saved_re: Option<&Regex> = ctx.get_aux(0)?;
|
||||||
let new_re = match saved_re {
|
let new_re = match saved_re {
|
||||||
None => {
|
None => {
|
||||||
let s = ctx.get::<String>(0)?;
|
let s = ctx.get::<String>(0)?;
|
||||||
match Regex::new(&s) {
|
match Regex::new(&s) {
|
||||||
Ok(r) => Some(r),
|
Ok(r) => Some(r),
|
||||||
Err(err) => return Err(rusqlite::Error::UserFunctionError(Box::new(err))),
|
Err(err) => return Err(rusqlite::Error::UserFunctionError(Box::new(err))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Some(_) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_match = {
|
||||||
|
let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap());
|
||||||
|
|
||||||
|
let text = ctx
|
||||||
|
.get_raw(1)
|
||||||
|
.as_str()
|
||||||
|
.map_err(|e| rusqlite::Error::UserFunctionError(e.into()))?;
|
||||||
|
|
||||||
|
re.is_match(text)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(re) = new_re {
|
||||||
|
ctx.set_aux(0, re);
|
||||||
}
|
}
|
||||||
Some(_) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_match = {
|
Ok(is_match)
|
||||||
let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap());
|
},
|
||||||
|
)
|
||||||
let text = ctx
|
|
||||||
.get_raw(1)
|
|
||||||
.as_str()
|
|
||||||
.map_err(|e| rusqlite::Error::UserFunctionError(e.into()))?;
|
|
||||||
|
|
||||||
re.is_match(text)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(re) = new_re {
|
|
||||||
ctx.set_aux(0, re);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(is_match)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch schema version from database.
|
/// Fetch schema version from database.
|
||||||
|
|
Loading…
Reference in a new issue