search = async function (q) {
  if (!q || typeof q !== 'string') {
    throw new BadParamError('q must be a string')
  }

  var release = await lock('history-db')
  try {
    // prep search terms
    q = q
      .toLowerCase() // all lowercase. (uppercase is interpretted as a directive by sqlite.)
      .replace(/[:^*]/g, '') // strip symbols that sqlite interprets
      .replace(/[-]/g, ' ') + // strip symbols that sqlite interprets
      '*' // allow partial matches

    // run query
    return await db.all(`
      SELECT offsets(visit_fts) as offsets, visit_fts.url, visit_fts.title, visit_stats.num_visits
        FROM visit_fts
        LEFT JOIN visit_stats ON visit_stats.url = visit_fts.url
        WHERE visit_fts MATCH ? AND visit_stats.num_visits > 2
        ORDER BY visit_stats.num_visits DESC
        LIMIT 10;
    `, [q])
  } finally {
    release()
  }
}
@param {string} q
@returns {Promise<Array<VisitSearchResult>>}