Blame db/cache.py

b838e2
b838e2
import threading
b838e2
b838e2
b838e2
class CacheItem:
b838e2
  def __init__(self, owner, key, value):
b838e2
    self.owner = owner
b838e2
    self.key = key
b838e2
    self.value = value
b838e2
    
b838e2
    self.prev = None
b838e2
    self.next = self.owner.first
b838e2
    if self.next:
b838e2
      self.next.prev = self
b838e2
    else:
b838e2
      self.owner.last = self
b838e2
    self.owner.first = self
b838e2
  
b838e2
  def touch(self):
b838e2
    if not self.prev:
b838e2
      return
b838e2
    self.prev.next = self.next
b838e2
    if self.next:
b838e2
      self.next.prev = self.prev
b838e2
    else:
b838e2
      self.owner.last = self.prev
b838e2
    return self.value
b838e2
b838e2
  def detouch(self):
b838e2
    if self.prev:
b838e2
      self.prev.next = self.next
b838e2
    else:
b838e2
      self.owner.first = self.next
b838e2
    if self.next:
b838e2
      self.next.prev = self.prev
b838e2
    else:
b838e2
      self.owner.last = self.prev
b838e2
b838e2
b838e2
class CacheTable:
b838e2
  def __init__(self, maxcount):
b838e2
    self.items = dict()
b838e2
    self.first = None
b838e2
    self.last = None
b838e2
    self.maxcount = maxcount
b838e2
    
b838e2
  def get(self, key):
b838e2
    item = self.items.get(key, None)
b838e2
    return item.touch() if item else None
b838e2
  
b838e2
  def set(self, key, value):
b838e2
    item = self.items.get(key, None)
b838e2
    if item:
b838e2
      item.value = value
b838e2
      item.touch()
b838e2
      return
b838e2
    self.items[key] = CacheItem(self, key, value)
b838e2
    while len(self.items) > self.maxcount:
b838e2
      del self.items[self.last.key]
b838e2
      self.last.detouch()
b838e2
b838e2
  def unset(self, key):
b838e2
    item = self.items.get(key, None)
b838e2
    if item:
b838e2
      del self.items[key]
b838e2
      item.detouch()
b838e2
    
b838e2
  def clear(self):
b838e2
    self.first = None
b838e2
    self.last = None
b838e2
    # remove cyclic references to help GC
b838e2
    for v in self.items.values():
b838e2
      v.prev = None
b838e2
      v.next = None
b838e2
    self.items.clear()
b838e2
    self.count = 0
b838e2
b838e2
b838e2
class Cache:
b838e2
  def __init__(self, server):
b838e2
    self.server = server
b838e2
    self.lock = threading.Lock()
b838e2
    self.tables = dict()
b838e2
    self.maxcount = self.server.config['db']['cache']['maxcount']
b838e2
  
b838e2
  def create_connection(self, connection):
b838e2
    return CacheConnection(self, connection)
b838e2
  
b838e2
  def clear(self):
b838e2
    with self.lock:
b838e2
      for t in self.tables.values():
b838e2
        t.clear()
b838e2
      self.tables.clear()
b838e2
  
b838e2
  def build_select(self, connection, table, fields):
b838e2
    assert(type(fields) is dict)
b838e2
    where = list()
b838e2
    args = list()
b838e2
    for k, v in fields.items():
b838e2
      assert(type(k) is str)
b838e2
      if type(v) is int:
b838e2
        where.append('%F=%d')
b838e2
        args.append(k)
b838e2
        args.append(v)
b838e2
      elif type(v) is str:
b838e2
        where.append('%F=%s')
b838e2
        args.append(k)
b838e2
        args.append(v)
b838e2
      else:
b838e2
        assert(False)
b838e2
    where = ' AND '.join(where) if where else '1'
b838e2
    return connection.parse('SELECT * FROM %T WHERE ' + where, table, *args)
b838e2
    
b838e2
  def select(self, connection, table, fields):
b838e2
    assert(type(table) is str)
b838e2
    sql = self.build_select(connection, table, fields)
b838e2
    
e5b0ac
    rows = None
e5b0ac
e5b0ac
    # cache does not support transactions and disabled
e5b0ac
    #
e5b0ac
    #with self.lock:
e5b0ac
    #  tbl = self.tables.get(table)
e5b0ac
    #  if not tbl:
e5b0ac
    #    self.tables[table] = tbl = CacheTable(self.maxcount)
e5b0ac
    #  rows = tbl.get(sql)
b838e2
     
b838e2
    if rows is None:
b838e2
      rows = connection.query_dict(sql)
b838e2
      assert(type(rows) is list)
e5b0ac
      #with self.lock:
e5b0ac
      #  tbl = self.tables.get(table)
e5b0ac
      #  if not tbl:
e5b0ac
      #    self.tables[table] = tbl = CacheTable(self.maxcount)
e5b0ac
      #  tbl.set(sql, rows)
e5b0ac
    
b838e2
    return rows
b838e2
b838e2
  def reset(self, connection, table, fields = None):
b838e2
    assert(type(table) is str)
e5b0ac
    assert(not connection.readonly)
e5b0ac
    return
b838e2
    sql = None
b838e2
    if not fields is None:
b838e2
      sql = self.build_select(connection, table, fields)
b838e2
    with self.lock:
b838e2
      tbl = self.tables.get(table)
b838e2
      if tbl:
b838e2
        if sql is None:
b838e2
          tbl.clear()
b838e2
        else:
b838e2
          tbl.unset(sql)
b838e2
    
b838e2
b838e2
class CacheConnection:
b838e2
  def __init__(self, cache, connection):
b838e2
    self.cache = cache
b838e2
    self.connection = connection
b838e2
b838e2
  def clear(self):
b838e2
    self.cache.clear()
b838e2
b838e2
  def select(self, table, fields):
b838e2
    return self.cache.select(self.connection, table, fields)
b838e2
b838e2
  def reset(self, table, fields = None):
b838e2
    self.cache.reset(self.connection, table, fields)
b838e2
b838e2
  def row(self, table, id):
b838e2
    rows = self.select(table, {'id': id})
b838e2
    if len(rows) == 0:
b838e2
      return None
b838e2
    assert(len(rows) == 1)
b838e2
    return rows[0]
b838e2
b838e2
  def reset_row(self, table, id):
b838e2
    self.reset(table, {'id': id})
b838e2