Allow searching for missing custom data properties (#2626)

* Allow searching for missing custom data properties

* Add has-cd query
This commit is contained in:
Abdo 2023-09-01 07:13:31 +03:00 committed by GitHub
parent 9d3f01043b
commit 064b973a2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 0 deletions

View file

@ -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)?,
}) })

View file

@ -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,

View file

@ -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)),
} }
} }

View file

@ -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)> {