                   where f.fieldName = 'extra' and not i.itemID in (select itemID from deletedItems)"
 
   @pref.prefs.clearUserPref('brace-all')
-  Zotero.DB.query('create table if not exists betterbibtex._version_ (tablename primary key, version not null, unique (tablename, version))')
-  Zotero.DB.query("insert or ignore into betterbibtex._version_ (tablename, version) values ('keys', 0)")
-
-  version = Zotero.DB.valueQuery("select version from betterbibtex._version_ where tablename = 'keys'")
+
+  Zotero.DB.query("create table if not exists betterbibtex.schema (lock primary key default 'schema' check (lock='schema'), version not null)")
+
+  ### migrate '_version_' to 'schema' ###
+  if @SQLColumns('_version_')
+    Zotero.DB.query("insert or replace into betterbibtex.schema (lock, version) select 'schema', version from betterbibtex._version_ order by version desc limit 1")
+    Zotero.DB.query('drop table betterbibtex._version_')
+
+  ### initialize 'schema' ###
+  Zotero.DB.query("insert or ignore into betterbibtex.schema (lock, version) values ('schema', 0)")
+  version = Zotero.DB.valueQuery("select version from betterbibtex.schema")
   @log("Booting BBT, schema: #{version}")
-  if version < 1
-    Zotero.DB.query('create table betterbibtex.keys (itemID primary key, libraryID not null, citekey not null, pinned)')
-
-  if version < 3
-    @pref.set('scan-citekeys', true)
-
-  if version < 4
-    Zotero.DB.query('alter table betterbibtex.keys rename to keys2')
-    Zotero.DB.query('create table betterbibtex.keys (itemID primary key, libraryID not null, citekey not null, citeKeyFormat)')
-    Zotero.DB.query('insert into betterbibtex.keys (itemID, libraryID, citekey, citeKeyFormat)
-               select itemID, libraryID, citekey, case when pinned = 1 then null else ? end from betterbibtex.keys2', [@pref.get('citeKeyFormat')])
-
-  if version < 5
-    Zotero.DB.query('drop table betterbibtex.keys2')
-
-  if version < 6
-    Zotero.DB.query("
-      create table betterbibtex.cache (
-        itemID not null,
-        context not null,
-        citekey not null,
-        entry not null,
-        primary key (itemid, context))
-      ")
-
-  if version < 7
+
+  ### upgrade 'keys' table ###
+  columns = @SQLColumns('keys')
+  if columns?.pinned || columns?.libraryID
     Zotero.DB.query('alter table betterbibtex.keys rename to _keys_')
+  if !columns || columns?.pinned || columns?.libraryID
     Zotero.DB.query('create table betterbibtex.keys (itemID primary key, citekey not null, citeKeyFormat)')
-    Zotero.DB.query('insert into betterbibtex.keys (itemID, citekey, citeKeyFormat) select itemID, citekey, citeKeyFormat from betterbibtex._keys_')
+  switch
+    when columns?.pinned
+      Zotero.DB.query('insert into betterbibtex.keys (itemID, citekey, citeKeyFormat)
+                       select itemID, citekey, case when pinned = 1 then null else ? end from betterbibtex._keys_', [@pref.get('citeKeyFormat')])
+    when columns?.libraryID
+      Zotero.DB.query('insert into betterbibtex.keys (itemID, citekey, citeKeyFormat)
+                       select itemID, citekey, citeKeyFormat from betterbibtex._keys_')
+  if columns?.pinned || columns?.libraryID
     Zotero.DB.query('drop table betterbibtex._keys_')
-    @pref.set('scan-citekeys', true)
-
-  if version < 8
-    Zotero.DB.query("
-      create table if not exists betterbibtex.autoexport (
-        id integer primary key not null,
-        collection_id not null,
-        collection_name not null,
-        path not null,
-        context not null,
-        recursive not null,
-        status not null,
-        unique (collection_id, path, context))
-      ")
-
-  if version < 10
-    Zotero.DB.query('alter table betterbibtex.cache rename to _cache_')
+
+  ### drop 'keys2' table, upgrade leftovers ###
+  Zotero.DB.query('drop table betterbibtex.keys2') if @SQLColumns('keys2')
+
+  ### upgrade 'cache' table ###
+  columns = @SQLColumns('cache')
+  unless columns?.lastaccess
+    if columns
+      Zotero.DB.query('alter table betterbibtex.cache rename to _cache_')
     Zotero.DB.query("
       create table betterbibtex.cache (
         itemID not null,

        lastaccess not null default CURRENT_TIMESTAMP,
        primary key (itemid, context))
      ")
    Zotero.DB.query('insert into betterbibtex.cache (itemID, context, citekey, entry) select itemID, context, citekey, entry from betterbibtex._cache_')
    Zotero.DB.query('drop table betterbibtex._cache_')

  Zotero.DB.query("insert or replace into betterbibtex._version_ (tablename, version) values ('keys', 10)")

  Zotero.Translate.Export::Sandbox.BetterBibTeX = {
    __exposedProps__: {keymanager: 'r', cache: 'r'}

  throw 'format: no item\n' + (new Error('dummy')).stack if not item.itemType
  return item

class Zotero.BetterBibTeX.XmlNode
  constructor: (@namespace, @root, @doc) ->
    if !@doc
      @doc = Zotero.BetterBibTeX.document.implementation.createDocument(@namespace, @root, null)
      @root = @doc.documentElement

  serialize: -> Zotero.BetterBibTeX.serializer.serializeToString(@doc)

  alias: (names) ->
    for name in names
      @Node::[name] = do (name) -> (v...) -> XmlNode::add.apply(@, [{"#{name}": v[0]}].concat(v.slice(1)))
    return

  set: (node, attrs...) ->
    for attr in attrs
      for own name, value of attr
        switch
          when typeof value == 'function'
            value.call(new @Node(@namespace, node, @doc))

          when name == ''
            node.appendChild(@doc.createTextNode('' + value))

          else
            node.setAttribute(name, '' + value)
    return

  add: (content...) ->
    if typeof content[0] == 'object'
      for own name, attrs of content[0]
        continue if name == ''
        node = @doc.createElementNS(@namespace, name)
        @root.appendChild(node)
        content = [attrs].concat(content.slice(1))
        break # there really should only be one pair here!
    node ?= @root

    content = (c for c in content when typeof c == 'number' || c)

    for attrs in content
      switch
        when typeof attrs == 'string'
          node.appendChild(@doc.createTextNode(attrs))

        when typeof attrs == 'function'
          attrs.call(new @Node(@namespace, node, @doc))

        when attrs.appendChild
          node.appendChild(attrs)

        else
          @set(node, attrs)

    return

class Zotero.BetterBibTeX.HTMLNode extends Zotero.BetterBibTeX.XmlNode
  constructor: (@namespace, @root, @doc) ->
    super(@namespace, @root, @doc)

  Node: HTMLNode

  HTMLNode::alias(['b', 'p', 'div', 'ul', 'li'])

require('preferences.coffee')
require('keymanager.coffee')
require('web-endpoints.coffee')
