update schema to fix default initial ease in deck configs

Closes #766

- changes the on-disk representation from % to a multiplier,
eg 250 -> 2.5, as this is consistent with the other options
- resets deck configs at or below 1.3 to 2.5
- for any cards that were using a reset deck config, reset their
current factor if it's at or below 2.0x. The cutoff is arbitrary,
and just intended to make sure we catch cards the user has rated
Easy on multiple times. The existing due dates are left alone.
This commit is contained in:
Damien Elmes 2020-09-22 08:16:39 +10:00
parent 133fe8518b
commit 76acf04dc0
10 changed files with 93 additions and 16 deletions

View file

@ -14,7 +14,8 @@ pub use crate::backend_proto::{
DeckConfigInner, DeckConfigInner,
}; };
pub use schema11::{DeckConfSchema11, NewCardOrderSchema11}; pub use schema11::{DeckConfSchema11, NewCardOrderSchema11};
pub const INITIAL_EASE_FACTOR: u16 = 2500; /// Old deck config and cards table store 250% as 2500.
pub const INITIAL_EASE_FACTOR_THOUSANDS: u16 = 2500;
mod schema11; mod schema11;

View file

@ -1,7 +1,7 @@
// Copyright: Ankitects Pty Ltd and contributors // Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use super::{DeckConf, DeckConfID, INITIAL_EASE_FACTOR}; use super::{DeckConf, DeckConfID, INITIAL_EASE_FACTOR_THOUSANDS};
use crate::backend_proto::deck_config_inner::NewCardOrder; use crate::backend_proto::deck_config_inner::NewCardOrder;
use crate::backend_proto::DeckConfigInner; use crate::backend_proto::DeckConfigInner;
use crate::{serde::default_on_invalid, timestamp::TimestampSecs, types::Usn}; use crate::{serde::default_on_invalid, timestamp::TimestampSecs, types::Usn};
@ -153,7 +153,7 @@ impl Default for NewConfSchema11 {
NewConfSchema11 { NewConfSchema11 {
bury: false, bury: false,
delays: vec![1.0, 10.0], delays: vec![1.0, 10.0],
initial_factor: INITIAL_EASE_FACTOR, initial_factor: INITIAL_EASE_FACTOR_THOUSANDS,
ints: NewCardIntervals::default(), ints: NewCardIntervals::default(),
order: NewCardOrderSchema11::default(), order: NewCardOrderSchema11::default(),
per_day: 20, per_day: 20,
@ -237,7 +237,7 @@ impl From<DeckConfSchema11> for DeckConf {
reviews_per_day: c.rev.per_day, reviews_per_day: c.rev.per_day,
bury_new: c.new.bury, bury_new: c.new.bury,
bury_reviews: c.rev.bury, bury_reviews: c.rev.bury,
initial_ease: (c.new.initial_factor as f32) / 10.0, initial_ease: (c.new.initial_factor as f32) / 1000.0,
easy_multiplier: c.rev.ease4, easy_multiplier: c.rev.ease4,
hard_multiplier: c.rev.hard_factor, hard_multiplier: c.rev.hard_factor,
lapse_multiplier: c.lapse.mult, lapse_multiplier: c.lapse.mult,
@ -298,7 +298,7 @@ impl From<DeckConf> for DeckConfSchema11 {
new: NewConfSchema11 { new: NewConfSchema11 {
bury: i.bury_new, bury: i.bury_new,
delays: i.learn_steps, delays: i.learn_steps,
initial_factor: (i.initial_ease * 10.0) as u16, initial_factor: (i.initial_ease * 1000.0) as u16,
ints: NewCardIntervals { ints: NewCardIntervals {
good: i.graduating_interval_good as u16, good: i.graduating_interval_good as u16,
easy: i.graduating_interval_easy as u16, easy: i.graduating_interval_easy as u16,

View file

@ -3,7 +3,7 @@
use crate::{ use crate::{
card::{Card, CardQueue, CardType}, card::{Card, CardQueue, CardType},
deckconf::INITIAL_EASE_FACTOR, deckconf::INITIAL_EASE_FACTOR_THOUSANDS,
}; };
impl Card { impl Card {
@ -29,7 +29,7 @@ impl Card {
self.interval = 0; self.interval = 0;
self.due = 0; self.due = 0;
self.original_due = 0; self.original_due = 0;
self.ease_factor = INITIAL_EASE_FACTOR; self.ease_factor = INITIAL_EASE_FACTOR_THOUSANDS;
} }
} }

View file

@ -4,7 +4,7 @@
use crate::{ use crate::{
card::{Card, CardID, CardQueue, CardType}, card::{Card, CardID, CardQueue, CardType},
collection::Collection, collection::Collection,
deckconf::INITIAL_EASE_FACTOR, deckconf::INITIAL_EASE_FACTOR_THOUSANDS,
decks::DeckID, decks::DeckID,
err::Result, err::Result,
notes::NoteID, notes::NoteID,
@ -24,7 +24,7 @@ impl Card {
if self.ease_factor == 0 { if self.ease_factor == 0 {
// unlike the old Python code, we leave the ease factor alone // unlike the old Python code, we leave the ease factor alone
// if it's already set // if it's already set
self.ease_factor = INITIAL_EASE_FACTOR; self.ease_factor = INITIAL_EASE_FACTOR_THOUSANDS;
} }
} }

View file

@ -4,7 +4,7 @@
use crate::{ use crate::{
card::{Card, CardID, CardQueue, CardType}, card::{Card, CardID, CardQueue, CardType},
collection::Collection, collection::Collection,
deckconf::INITIAL_EASE_FACTOR, deckconf::INITIAL_EASE_FACTOR_THOUSANDS,
err::Result, err::Result,
}; };
use rand::distributions::{Distribution, Uniform}; use rand::distributions::{Distribution, Uniform};
@ -19,7 +19,7 @@ impl Card {
if self.ease_factor == 0 { if self.ease_factor == 0 {
// unlike the old Python code, we leave the ease factor alone // unlike the old Python code, we leave the ease factor alone
// if it's already set // if it's already set
self.ease_factor = INITIAL_EASE_FACTOR; self.ease_factor = INITIAL_EASE_FACTOR_THOUSANDS;
} }
} }
} }

View file

@ -0,0 +1,10 @@
update cards
set factor = 2500,
usn = ?,
mod = ?
where factor != 0
and factor <= 2000
and (
did in DECK_IDS
or odid in DECK_IDS
)

View file

@ -3,7 +3,8 @@
use crate::{ use crate::{
card::{Card, CardID, CardQueue, CardType}, card::{Card, CardID, CardQueue, CardType},
decks::{Deck, DeckID}, deckconf::DeckConfID,
decks::{Deck, DeckID, DeckKind},
err::Result, err::Result,
notes::NoteID, notes::NoteID,
sched::congrats::CongratsInfo, sched::congrats::CongratsInfo,
@ -18,6 +19,8 @@ use rusqlite::{
}; };
use std::{collections::HashSet, convert::TryFrom, result}; use std::{collections::HashSet, convert::TryFrom, result};
use super::ids_to_string;
impl FromSql for CardType { impl FromSql for CardType {
fn column_result(value: ValueRef<'_>) -> std::result::Result<Self, FromSqlError> { fn column_result(value: ValueRef<'_>) -> std::result::Result<Self, FromSqlError> {
if let ValueRef::Integer(i) = value { if let ValueRef::Integer(i) = value {
@ -350,6 +353,38 @@ impl super::SqliteStorage {
Ok(()) Ok(())
} }
/// Fix cards with low eases due to schema 15 bug.
/// Deck configs were defaulting to 2.5% ease, which was capped to
/// 130% when the deck options were edited for the first time.
pub(crate) fn fix_low_card_eases_for_configs(
&self,
configs: &[DeckConfID],
server: bool,
) -> Result<()> {
let mut affected_decks = vec![];
for conf in configs {
for (deck_id, _name) in self.get_all_deck_names()? {
if let Some(deck) = self.get_deck(deck_id)? {
if let DeckKind::Normal(normal) = &deck.kind {
if normal.config_id == conf.0 {
affected_decks.push(deck.id);
}
}
}
}
}
let mut ids = String::new();
ids_to_string(&mut ids, &affected_decks);
let sql = include_str!("fix_low_ease.sql").replace("DECK_IDS", &ids);
self.db
.prepare(&sql)?
.execute(params![self.usn(server)?, TimestampSecs::now()])?;
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -159,13 +159,35 @@ impl SqliteStorage {
pub(super) fn upgrade_deck_conf_to_schema15(&self) -> Result<()> { pub(super) fn upgrade_deck_conf_to_schema15(&self) -> Result<()> {
for conf in self.all_deck_config_schema14()? { for conf in self.all_deck_config_schema14()? {
let conf: DeckConf = conf.into(); let mut conf: DeckConf = conf.into();
// schema 15 stored starting ease of 2.5 as 250
conf.inner.initial_ease *= 100.0;
self.update_deck_conf(&conf)?; self.update_deck_conf(&conf)?;
} }
Ok(()) Ok(())
} }
// schema 15->16
pub(super) fn upgrade_deck_conf_to_schema16(&self, server: bool) -> Result<()> {
let mut invalid_configs = vec![];
for mut conf in self.all_deck_config()? {
// schema 16 changed starting ease of 250 to 2.5
conf.inner.initial_ease /= 100.0;
// new deck configs created with schema 15 had the wrong
// ease set - reset any deck configs at the minimum ease
// to the default 250%
if conf.inner.initial_ease <= 1.3 {
conf.inner.initial_ease = 2.5;
invalid_configs.push(conf.id);
}
self.update_deck_conf(&conf)?;
}
self.fix_low_card_eases_for_configs(&invalid_configs, server)
}
// schema 15->11 // schema 15->11
pub(super) fn downgrade_deck_conf_from_schema15(&self) -> Result<()> { pub(super) fn downgrade_deck_conf_from_schema15(&self) -> Result<()> {

View file

@ -12,9 +12,7 @@ use std::cmp::Ordering;
use std::{borrow::Cow, path::Path, sync::Arc}; use std::{borrow::Cow, path::Path, sync::Arc};
use unicase::UniCase; use unicase::UniCase;
const SCHEMA_MIN_VERSION: u8 = 11; use super::upgrades::{SCHEMA_MAX_VERSION, SCHEMA_MIN_VERSION, SCHEMA_STARTING_VERSION};
const SCHEMA_STARTING_VERSION: u8 = 11;
const SCHEMA_MAX_VERSION: u8 = 15;
fn unicase_compare(s1: &str, s2: &str) -> Ordering { fn unicase_compare(s1: &str, s2: &str) -> Ordering {
UniCase::new(s1).cmp(&UniCase::new(s2)) UniCase::new(s1).cmp(&UniCase::new(s2))

View file

@ -1,6 +1,13 @@
// Copyright: Ankitects Pty Ltd and contributors // Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
/// The minimum schema version we can open.
pub(super) const SCHEMA_MIN_VERSION: u8 = 11;
/// The version new files are initially created with.
pub(super) const SCHEMA_STARTING_VERSION: u8 = 11;
/// The maximum schema version we can open.
pub(super) const SCHEMA_MAX_VERSION: u8 = 16;
use super::SqliteStorage; use super::SqliteStorage;
use crate::err::Result; use crate::err::Result;
@ -20,6 +27,10 @@ impl SqliteStorage {
self.upgrade_decks_to_schema15(server)?; self.upgrade_decks_to_schema15(server)?;
self.upgrade_deck_conf_to_schema15()?; self.upgrade_deck_conf_to_schema15()?;
} }
if ver < 16 {
self.upgrade_deck_conf_to_schema16(server)?;
self.db.execute_batch("update col set ver = 16")?;
}
Ok(()) Ok(())
} }