mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00

Since Anki first moved to an SQL backend, it has stored fields in a fields table, with one field per line. This is a natural layout in a relational database, and it had some nice properties. It meant we could retrieve an individual field of a fact, which we used for limiting searches to a particular field, for sorting, and for determining if a field was unique, by adding an index on the field value. The index was very expensive, so as part of the early work towards 2.0 I added a checksum field instead, and added an index to that. This was a lot cheaper than storing the entire value twice for the purpose of fast searches, but it only partly solved the problem. We still needed an index on factId so that we could retrieve a given fact's fields quickly. For simple models this was fairly cheap, but as the number of fields grows the table grows very big. 25k facts with 30 fields each and the fields table has grown to 750k entries. This makes the factId index and checksum index really expensive - with the q/a cache removed, about 30% of the deck in such a situation. Equally problematic was sorting on those fields. Short of adding another expensive index, a sort involves a table scan of the entire table. We solve these problems by moving all fields into the facts table. For this to work, we need to address some issues: Sorting: we'll add an option to the model to specify the sort field. When facts are modified, that field is written to a separate sort column. It can be HTML stripped, and possibly truncated to a maximum number of letters. This means that switching sort to a different field involves an expensive rewrite of the sort column, but people tend to leave their sort field set to the same value, and we don't need to clear the field if the user switches temporarily to a non-field sort like due order. And it has the nice properties of allowing different models to be sorted on different columns at the same time, and makes it impossible for models to be hidden because the user has sorted on a field which doesn't appear in some models. Searching for words with embedded HTML: 1.2 introduced a HTML-stripped cache of the fields content, which both sped up searches (since we didn't have to search the possibly large fields table), and meant we could find "bob" in "b<b>ob</b>" quickly. The ability to quickly search for words peppered with HTML was nice, but it meant doubling the cost of storing text in many cases, and meant after any edit more data has to be written to the DB. Instead, we'll do it on the fly. On this i7 computer, stripping HTML from all fields takes 1-2.6 seconds on 25-50k decks. We could possibly skip the stripping for people who don't require it - the number of people who bold parts of words is actually pretty small. Duplicate detection: one option would be to fetch all fields when the add cards dialog or editor are opened. But this will be expensive on mobile devices. Instead, we'll create a separate table of (fid, csum), with an index on both columns. When we edit a fact, we delete all the existing checksums for that fact, and add checksums for any fields that must be checked as unique. We could optionally skip the index on csum - some benchmarking is required. As for the new table layout, creating separate columns for each field won't scale. Instead, we store the fields in a single column, separated by an ascii record separator. We split on that character when extracting from the database, and join on it when writing to the DB. Searching on a particular field in the browser will be accomplished by finding all facts that match, and then unpacking to see if the relevant field matched. Tags have been moved back to a separate column. Now that fields are on the facts table, there is no need to pack them in as a field simply to avoid another table hit.
55 lines
1.3 KiB
Python
55 lines
1.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
|
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
|
|
|
from anki.models import Model, Template
|
|
from anki.lang import _
|
|
|
|
models = []
|
|
|
|
# Basic
|
|
##########################################################################
|
|
|
|
def BasicModel(deck):
|
|
m = Model(deck)
|
|
m.name = _("Basic")
|
|
fm = m.newField()
|
|
fm['name'] = _("Front")
|
|
fm['req'] = True
|
|
fm['uniq'] = True
|
|
m.addField(fm)
|
|
fm = m.newField()
|
|
fm['name'] = _("Back")
|
|
m.addField(fm)
|
|
t = Template(deck)
|
|
t.name = _("Forward")
|
|
t.qfmt = "{{" + _("Front") + "}}"
|
|
t.afmt = "{{" + _("Back") + "}}"
|
|
m.addTemplate(t)
|
|
t = Template(deck)
|
|
t.name = _("Reverse")
|
|
t.qfmt = "{{" + _("Back") + "}}"
|
|
t.afmt = "{{" + _("Front") + "}}"
|
|
t.active = False
|
|
m.addTemplate(t)
|
|
return m
|
|
|
|
models.append(BasicModel)
|
|
|
|
# Recovery
|
|
##########################################################################
|
|
|
|
def RecoveryModel():
|
|
m.name = _("Recovery")
|
|
fm = Field(deck)
|
|
fm.name = _("Question")
|
|
m.addField(fm)
|
|
fm = Field(deck)
|
|
fm.name = _("Back")
|
|
m.addField(fm)
|
|
t = Template(deck)
|
|
t.name = _("Forward")
|
|
t.qfmt = "{{" + _("Question") + "}}"
|
|
t.afmt = "{{" + _("Back") + "}}"
|
|
m.addTemplate(t)
|
|
return m
|