restore the deletion log

the initial plan was to zero the creation time and leave the cards/facts there
until we have a chance to garbage collect them on a schema change, but such an
approach won't work with deck subscriptions
This commit is contained in:
Damien Elmes 2011-05-04 19:00:38 +09:00
parent f2604b7805
commit 3d370f675b
6 changed files with 39 additions and 62 deletions

View file

@ -10,7 +10,7 @@ from anki.utils import intTime, hexifyID
# Type: 0=new, 1=learning, 2=due # Type: 0=new, 1=learning, 2=due
# Queue: same as above, and: # Queue: same as above, and:
# -1=suspended, -2=user buried, -3=sched buried, -4=deleted # -1=suspended, -2=user buried, -3=sched buried
# Due is used differently for different queues. # Due is used differently for different queues.
# - new queue: fact id or random int # - new queue: fact id or random int
# - rev queue: integer day # - rev queue: integer day

View file

@ -20,6 +20,10 @@ REV_CARDS_OLD_FIRST = 0
REV_CARDS_NEW_FIRST = 1 REV_CARDS_NEW_FIRST = 1
REV_CARDS_RANDOM = 2 REV_CARDS_RANDOM = 2
# deletion types
DEL_CARD = 0
DEL_FACT = 1
# Labels # Labels
########################################################################## ##########################################################################

View file

@ -152,8 +152,6 @@ qconf=?, conf=?, data=?""",
if not self.schemaChanged(): if not self.schemaChanged():
if check and not runFilter("modSchema", True): if check and not runFilter("modSchema", True):
raise AnkiError("abortSchemaMod") raise AnkiError("abortSchemaMod")
# next sync will be full
self.emptyTrash()
self.scm = intTime() self.scm = intTime()
def schemaChanged(self): def schemaChanged(self):
@ -213,11 +211,18 @@ qconf=?, conf=?, data=?""",
self.modelCache = {} self.modelCache = {}
self.sched.reset() self.sched.reset()
# Deletion logging
##########################################################################
def _logDels(self, ids, type):
self.db.executemany("insert into graves values (%d, ?, %d)" % (
intTime(), type), ([x] for x in ids))
# Facts # Facts
########################################################################## ##########################################################################
def factCount(self): def factCount(self):
return self.db.scalar("select count() from facts where crt != 0") return self.db.scalar("select count() from facts")
def newFact(self): def newFact(self):
"Return a new fact with the current model." "Return a new fact with the current model."
@ -251,13 +256,7 @@ qconf=?, conf=?, data=?""",
strids = ids2str(ids) strids = ids2str(ids)
self.db.execute("delete from facts where id in %s" % strids) self.db.execute("delete from facts where id in %s" % strids)
self.db.execute("delete from fsums where fid in %s" % strids) self.db.execute("delete from fsums where fid in %s" % strids)
self._logDels(ids, DEL_FACT)
def _delDanglingFacts(self):
"Delete any facts without cards. Don't call this directly."
ids = self.db.list("""
select id from facts where id not in (select distinct fid from cards)""")
self._delFacts(ids)
return ids
# Card creation # Card creation
########################################################################## ##########################################################################
@ -341,41 +340,23 @@ select id from facts where id not in (select distinct fid from cards)""")
########################################################################## ##########################################################################
def cardCount(self): def cardCount(self):
return self.db.scalar("select count() from cards where crt != 0") return self.db.scalar("select count() from cards")
def delCard(self, id):
"Delete a card given its id. Delete any unused facts."
self.delCards([id])
def delCards(self, ids): def delCards(self, ids):
"Bulk delete cards by ID." "Bulk delete cards by ID."
if not ids: if not ids:
return return
sids = ids2str(ids) sids = ids2str(ids)
if self.schemaChanged(): fids = self.db.list("select fid from cards where id in "+sids)
# immediate delete? # remove cards
self.db.execute("delete from cards where id in %s" % sids) self.db.execute("delete from cards where id in "+sids)
self.db.execute("delete from revlog where cid in %s" % sids) self.db.execute("delete from revlog where cid in "+sids)
# remove any dangling facts self._logDels(ids, DEL_CARD)
self._delDanglingFacts() # then facts
else: fids = self.db.list("""
# trash select id from facts where id in %s and id not in (select fid from cards)""" %
sfids = ids2str( ids2str(fids))
self.db.list("select fid from cards where id in "+sids)) self._delFacts(fids)
# need to handle delete of fsums/revlog remotely after sync
self.db.execute(
"update cards set crt = 0, queue = -4, mod = ? where id in "+sids,
intTime())
self.db.execute(
"update facts set crt = 0, mod = ? where id in "+sfids,
intTime())
self.db.execute("delete from fsums where fid in "+sfids)
self.db.execute("delete from revlog where cid in "+sids)
def emptyTrash(self):
self.db.executescript("""
delete from facts where id in (select fid from cards where queue = -4);
delete from cards where queue = -4;""")
# Models # Models
########################################################################## ##########################################################################
@ -783,6 +764,10 @@ update facts set tags = :t, mod = :n where id = :id""", [fix(row) for row in res
problems = [] problems = []
self.save() self.save()
oldSize = os.stat(self.path)[stat.ST_SIZE] oldSize = os.stat(self.path)[stat.ST_SIZE]
# delete any facts with missing cards
ids = self.db.list("""
select id from facts where id not in (select distinct fid from cards)""")
self._delFacts(ids)
# tags # tags
self.db.execute("delete from tags") self.db.execute("delete from tags")
self.updateFactTags() self.updateFactTags()

View file

@ -135,6 +135,12 @@ create table if not exists gconf (
conf text not null conf text not null
); );
create table if not exists graves (
time integer not null,
oid integer not null,
type integer not null
);
create table if not exists revlog ( create table if not exists revlog (
time integer primary key, time integer primary key,
cid integer not null, cid integer not null,

View file

@ -53,12 +53,10 @@ def test_delete():
f['Back'] = u'2' f['Back'] = u'2'
deck.addFact(f) deck.addFact(f)
cid = f.cards()[0].id cid = f.cards()[0].id
# when the schema is dirty, deletion should be immediate
assert deck.schemaChanged() == True
deck.reset() deck.reset()
deck.sched.answerCard(deck.sched.getCard(), 2) deck.sched.answerCard(deck.sched.getCard(), 2)
assert deck.db.scalar("select count() from revlog") == 1 assert deck.db.scalar("select count() from revlog") == 1
deck.delCard(cid) deck.delCards([cid])
assert deck.cardCount() == 0 assert deck.cardCount() == 0
assert deck.factCount() == 0 assert deck.factCount() == 0
assert deck.db.scalar("select count() from facts") == 0 assert deck.db.scalar("select count() from facts") == 0
@ -68,26 +66,10 @@ def test_delete():
# add the fact back # add the fact back
deck.addFact(f) deck.addFact(f)
assert deck.cardCount() == 1 assert deck.cardCount() == 1
# mark the schema as clean
deck.lastSync = deck.scm + 1
# and deck as syncable
deck.syncName = "abc"
# cards/facts should go in the deletion log instead
cid = f.cards()[0].id cid = f.cards()[0].id
deck.delCard(cid) deck.delCards([cid])
assert deck.cardCount() == 0 assert deck.cardCount() == 0
assert deck.factCount() == 0 assert deck.factCount() == 0
assert deck.db.scalar("select count() from facts") == 1
assert deck.db.scalar("select count() from cards") == 1
assert deck.db.scalar("select 1 from cards where crt = 0") == 1
assert deck.db.scalar("select 1 from facts where crt = 0") == 1
assert deck.db.scalar("select queue from cards") == -4
# modifying the schema should empty the trash
deck.modSchema()
assert deck.cardCount() == 0
assert deck.factCount() == 0
assert deck.db.scalar("select count() from facts") == 0
assert deck.db.scalar("select count() from cards") == 0
def test_misc(): def test_misc():
d = getEmptyDeck() d = getEmptyDeck()

View file

@ -74,11 +74,11 @@ def test_factAddDelete():
id1 = cards[0].id; id2 = cards[1].id id1 = cards[0].id; id2 = cards[1].id
assert deck.cardCount() == 2 assert deck.cardCount() == 2
assert deck.factCount() == 1 assert deck.factCount() == 1
deck.delCard(id1) deck.delCards([id1])
assert deck.cardCount() == 1 assert deck.cardCount() == 1
assert deck.factCount() == 1 assert deck.factCount() == 1
# and the second should clear the fact # and the second should clear the fact
deck.delCard(id2) deck.delCards([id2])
assert deck.cardCount() == 0 assert deck.cardCount() == 0
assert deck.factCount() == 0 assert deck.factCount() == 0