parent
f1ddfb7af6
commit
40a68afad3
@ -6,11 +6,13 @@ use nom::branch::alt;
|
|||||||
use nom::bytes::complete::escaped;
|
use nom::bytes::complete::escaped;
|
||||||
use nom::bytes::complete::is_not;
|
use nom::bytes::complete::is_not;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::alphanumeric1;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::char;
|
use nom::character::complete::char;
|
||||||
use nom::character::complete::none_of;
|
use nom::character::complete::none_of;
|
||||||
use nom::character::complete::one_of;
|
use nom::character::complete::one_of;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::error::ErrorKind as NomErrorKind;
|
use nom::error::ErrorKind as NomErrorKind;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
@ -101,6 +103,7 @@ pub enum PropertyKind {
|
|||||||
Ease(f32),
|
Ease(f32),
|
||||||
Position(u32),
|
Position(u32),
|
||||||
Rated(i32, RatingKind),
|
Rated(i32, RatingKind),
|
||||||
|
CustomDataNumber { key: String, value: f32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@ -403,6 +406,7 @@ fn parse_prop(prop_clause: &str) -> ParseResult<SearchNode> {
|
|||||||
tag("pos"),
|
tag("pos"),
|
||||||
tag("rated"),
|
tag("rated"),
|
||||||
tag("resched"),
|
tag("resched"),
|
||||||
|
recognize(preceded(tag("cdn:"), alphanumeric1)),
|
||||||
))(prop_clause)
|
))(prop_clause)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
parse_failure(
|
parse_failure(
|
||||||
@ -442,7 +446,13 @@ fn parse_prop(prop_clause: &str) -> ParseResult<SearchNode> {
|
|||||||
"reps" => PropertyKind::Reps(parse_u32(num, prop_clause)?),
|
"reps" => PropertyKind::Reps(parse_u32(num, prop_clause)?),
|
||||||
"lapses" => PropertyKind::Lapses(parse_u32(num, prop_clause)?),
|
"lapses" => PropertyKind::Lapses(parse_u32(num, prop_clause)?),
|
||||||
"pos" => PropertyKind::Position(parse_u32(num, prop_clause)?),
|
"pos" => PropertyKind::Position(parse_u32(num, prop_clause)?),
|
||||||
_ => unreachable!(),
|
other => {
|
||||||
|
let Some(prop) = other.strip_prefix("cdn:") else { unreachable!() };
|
||||||
|
PropertyKind::CustomDataNumber {
|
||||||
|
key: prop.into(),
|
||||||
|
value: parse_f32(num, prop_clause)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(SearchNode::Property {
|
Ok(SearchNode::Property {
|
||||||
@ -903,6 +913,16 @@ mod test {
|
|||||||
kind: PropertyKind::Ease(3.3)
|
kind: PropertyKind::Ease(3.3)
|
||||||
})]
|
})]
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse("prop:cdn:abc<=1")?,
|
||||||
|
vec![Search(Property {
|
||||||
|
operator: "<=".into(),
|
||||||
|
kind: PropertyKind::CustomDataNumber {
|
||||||
|
key: "abc".into(),
|
||||||
|
value: 1.0
|
||||||
|
}
|
||||||
|
})]
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1112,6 +1132,18 @@ mod test {
|
|||||||
provided: "DUE<5".into(),
|
provided: "DUE<5".into(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
assert_err_kind(
|
||||||
|
"prop:cdn=5",
|
||||||
|
InvalidPropProperty {
|
||||||
|
provided: "cdn=5".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_err_kind(
|
||||||
|
"prop:cdn:=5",
|
||||||
|
InvalidPropProperty {
|
||||||
|
provided: "cdn:=5".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
assert_err_kind(
|
assert_err_kind(
|
||||||
"prop:lapses",
|
"prop:lapses",
|
||||||
|
@ -354,6 +354,13 @@ impl SqlWriter<'_> {
|
|||||||
write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32).unwrap()
|
write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32).unwrap()
|
||||||
}
|
}
|
||||||
PropertyKind::Rated(days, ease) => self.write_rated(op, i64::from(*days), ease)?,
|
PropertyKind::Rated(days, ease) => self.write_rated(op, i64::from(*days), ease)?,
|
||||||
|
PropertyKind::CustomDataNumber { key, value } => {
|
||||||
|
write!(
|
||||||
|
self.sql,
|
||||||
|
"extract_custom_data_number(c.data, '{key}') {op} {value}"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -1166,6 +1173,10 @@ mod test {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(s(ctx, "prop:rated>-5:3").0, s(ctx, "rated:5:3").0);
|
assert_eq!(s(ctx, "prop:rated>-5:3").0, s(ctx, "rated:5:3").0);
|
||||||
|
assert_eq!(
|
||||||
|
&s(ctx, "prop:cdn:r=1").0,
|
||||||
|
"(extract_custom_data_number(c.data, 'r') = 1)"
|
||||||
|
);
|
||||||
|
|
||||||
// note types by name
|
// note types by name
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -171,6 +171,7 @@ fn write_property(operator: &str, kind: &PropertyKind) -> String {
|
|||||||
RatingKind::AnyAnswerButton => format!("prop:rated{}{}", operator, u),
|
RatingKind::AnyAnswerButton => format!("prop:rated{}{}", operator, u),
|
||||||
RatingKind::ManualReschedule => format!("prop:resched{}{}", operator, u),
|
RatingKind::ManualReschedule => format!("prop:resched{}{}", operator, u),
|
||||||
},
|
},
|
||||||
|
CustomDataNumber { key, value } => format!("prop:cdn:{key}{operator}{value}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ use regex::Regex;
|
|||||||
use rusqlite::functions::FunctionFlags;
|
use rusqlite::functions::FunctionFlags;
|
||||||
use rusqlite::params;
|
use rusqlite::params;
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
use serde_json::Value;
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
|
|
||||||
use super::upgrades::SCHEMA_MAX_VERSION;
|
use super::upgrades::SCHEMA_MAX_VERSION;
|
||||||
@ -24,6 +25,7 @@ use crate::error::DbErrorKind;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::scheduler::timing::local_minutes_west_for_stamp;
|
use crate::scheduler::timing::local_minutes_west_for_stamp;
|
||||||
use crate::scheduler::timing::v1_creation_date;
|
use crate::scheduler::timing::v1_creation_date;
|
||||||
|
use crate::storage::card::data::CardData;
|
||||||
use crate::text::without_combining;
|
use crate::text::without_combining;
|
||||||
|
|
||||||
fn unicase_compare(s1: &str, s2: &str) -> Ordering {
|
fn unicase_compare(s1: &str, s2: &str) -> Ordering {
|
||||||
@ -67,6 +69,7 @@ fn open_or_create_collection_db(path: &Path) -> Result<Connection> {
|
|||||||
add_regexp_tags_function(&db)?;
|
add_regexp_tags_function(&db)?;
|
||||||
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)?;
|
||||||
|
|
||||||
db.create_collation("unicase", unicase_compare)?;
|
db.create_collation("unicase", unicase_compare)?;
|
||||||
|
|
||||||
@ -190,6 +193,28 @@ fn add_regexp_tags_function(db: &Connection) -> rusqlite::Result<()> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// eg. extract_custom_data_number(card.data, 'r') -> float | null
|
||||||
|
fn add_extract_custom_data_number_function(db: &Connection) -> rusqlite::Result<()> {
|
||||||
|
db.create_scalar_function(
|
||||||
|
"extract_custom_data_number",
|
||||||
|
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(None);
|
||||||
|
}
|
||||||
|
let Ok(key) = ctx.get_raw(1).as_str() else { return Ok(None) };
|
||||||
|
let custom_data = &CardData::from_str(card_data).custom_data;
|
||||||
|
let Ok(value) = serde_json::from_str::<Value>(custom_data) else { return Ok(None) };
|
||||||
|
let num = value.get(key).and_then(|v| v.as_f64());
|
||||||
|
Ok(num)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 New Issue
Block a user