mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
Allow searching for missing custom data properties (#2626)
* Allow searching for missing custom data properties * Add has-cd query
This commit is contained in:
parent
9d3f01043b
commit
064b973a2a
4 changed files with 34 additions and 0 deletions
|
@ -92,6 +92,7 @@ pub enum SearchNode {
|
||||||
Regex(String),
|
Regex(String),
|
||||||
NoCombining(String),
|
NoCombining(String),
|
||||||
WordBoundary(String),
|
WordBoundary(String),
|
||||||
|
CustomData(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
@ -348,6 +349,7 @@ fn search_node_for_text_with_argument<'a>(
|
||||||
"nc" => SearchNode::NoCombining(unescape(val)?),
|
"nc" => SearchNode::NoCombining(unescape(val)?),
|
||||||
"w" => SearchNode::WordBoundary(unescape(val)?),
|
"w" => SearchNode::WordBoundary(unescape(val)?),
|
||||||
"dupe" => parse_dupe(val)?,
|
"dupe" => parse_dupe(val)?,
|
||||||
|
"has-cd" => SearchNode::CustomData(unescape(val)?),
|
||||||
// anything else is a field search
|
// anything else is a field search
|
||||||
_ => parse_single_field(key, val)?,
|
_ => parse_single_field(key, val)?,
|
||||||
})
|
})
|
||||||
|
|
|
@ -179,6 +179,7 @@ impl SqlWriter<'_> {
|
||||||
write!(self.sql, "c.id in ({})", cids).unwrap();
|
write!(self.sql, "c.id in ({})", cids).unwrap();
|
||||||
}
|
}
|
||||||
SearchNode::Property { operator, kind } => self.write_prop(operator, kind)?,
|
SearchNode::Property { operator, kind } => self.write_prop(operator, kind)?,
|
||||||
|
SearchNode::CustomData(key) => self.write_custom_data(key)?,
|
||||||
SearchNode::WholeCollection => write!(self.sql, "true").unwrap(),
|
SearchNode::WholeCollection => write!(self.sql, "true").unwrap(),
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -368,6 +369,12 @@ impl SqlWriter<'_> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_custom_data(&mut self, key: &str) -> Result<()> {
|
||||||
|
write!(self.sql, "has_custom_data(c.data, '{key}')").unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn write_state(&mut self, state: &StateKind) -> Result<()> {
|
fn write_state(&mut self, state: &StateKind) -> Result<()> {
|
||||||
let timing = self.col.timing_today()?;
|
let timing = self.col.timing_today()?;
|
||||||
match state {
|
match state {
|
||||||
|
@ -927,6 +934,7 @@ impl SearchNode {
|
||||||
SearchNode::Flag(_) => RequiredTable::Cards,
|
SearchNode::Flag(_) => RequiredTable::Cards,
|
||||||
SearchNode::CardIds(_) => RequiredTable::Cards,
|
SearchNode::CardIds(_) => RequiredTable::Cards,
|
||||||
SearchNode::Property { .. } => RequiredTable::Cards,
|
SearchNode::Property { .. } => RequiredTable::Cards,
|
||||||
|
SearchNode::CustomData { .. } => RequiredTable::Cards,
|
||||||
|
|
||||||
SearchNode::UnqualifiedText(_) => RequiredTable::Notes,
|
SearchNode::UnqualifiedText(_) => RequiredTable::Notes,
|
||||||
SearchNode::SingleField { .. } => RequiredTable::Notes,
|
SearchNode::SingleField { .. } => RequiredTable::Notes,
|
||||||
|
|
|
@ -86,6 +86,7 @@ fn write_search_node(node: &SearchNode) -> String {
|
||||||
Regex(s) => maybe_quote(&format!("re:{}", s)),
|
Regex(s) => maybe_quote(&format!("re:{}", s)),
|
||||||
NoCombining(s) => maybe_quote(&format!("nc:{}", s)),
|
NoCombining(s) => maybe_quote(&format!("nc:{}", s)),
|
||||||
WordBoundary(s) => maybe_quote(&format!("w:{}", s)),
|
WordBoundary(s) => maybe_quote(&format!("w:{}", s)),
|
||||||
|
CustomData(k) => maybe_quote(&format!("has-cd:{}", k)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ fn open_or_create_collection_db(path: &Path) -> Result<Connection> {
|
||||||
add_without_combining_function(&db)?;
|
add_without_combining_function(&db)?;
|
||||||
add_fnvhash_function(&db)?;
|
add_fnvhash_function(&db)?;
|
||||||
add_extract_custom_data_number_function(&db)?;
|
add_extract_custom_data_number_function(&db)?;
|
||||||
|
add_has_custom_data_function(&db)?;
|
||||||
|
|
||||||
db.create_collation("unicase", unicase_compare)?;
|
db.create_collation("unicase", unicase_compare)?;
|
||||||
|
|
||||||
|
@ -223,6 +224,28 @@ fn add_extract_custom_data_number_function(db: &Connection) -> rusqlite::Result<
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// eg. has_custom_data(card.data, 'r') -> bool
|
||||||
|
fn add_has_custom_data_function(db: &Connection) -> rusqlite::Result<()> {
|
||||||
|
db.create_scalar_function(
|
||||||
|
"has_custom_data",
|
||||||
|
2,
|
||||||
|
FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
|
move |ctx| {
|
||||||
|
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
||||||
|
|
||||||
|
let Ok(card_data) = ctx.get_raw(0).as_str() else { return Ok(None) };
|
||||||
|
if card_data.is_empty() {
|
||||||
|
return Ok(Some(false));
|
||||||
|
}
|
||||||
|
let Ok(key) = ctx.get_raw(1).as_str() else { return Ok(Some(false)) };
|
||||||
|
let custom_data = &CardData::from_str(card_data).custom_data;
|
||||||
|
let Ok(value) = serde_json::from_str::<Value>(custom_data) else { return Ok(Some(false)) };
|
||||||
|
|
||||||
|
Ok(value.get(key).map(|_| true))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetch schema version from database.
|
/// Fetch schema version from database.
|
||||||
/// Return (must_create, version)
|
/// Return (must_create, version)
|
||||||
fn schema_version(db: &Connection) -> Result<(bool, u8)> {
|
fn schema_version(db: &Connection) -> Result<(bool, u8)> {
|
||||||
|
|
Loading…
Reference in a new issue