Database abstraction layer
parent
e0260fbfa6
commit
bad93ef7ee
@ -0,0 +1 @@
|
|||||||
|
|
@ -0,0 +1,263 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
class Database(object):
|
||||||
|
def __init__(self, dbpath = "node.db"):
|
||||||
|
self.conn = sqlite3.connect(dbpath)
|
||||||
|
self.conn.row_factory = Row
|
||||||
|
self.tables = {}
|
||||||
|
|
||||||
|
def _get_cursor(self):
|
||||||
|
return self.conn.cursor()
|
||||||
|
|
||||||
|
def _table_create_nodes(self):
|
||||||
|
self.query("CREATE TABLE IF NOT EXISTS nodes (`id` INTEGER PRIMARY KEY, `uuid` TEXT, `host` TEXT, `port` NUMERIC, `pubkey` TEXT, `presupplied` NUMERIC, `attributes` NUMERIC);")
|
||||||
|
|
||||||
|
def _get_table(self, name, in_memory=False, forced=False):
|
||||||
|
if in_memory == True:
|
||||||
|
try:
|
||||||
|
# Only create a new MemoryTable if one doesn't already exist
|
||||||
|
create_new = forced or not isinstance(self.tables[name], MemoryTable)
|
||||||
|
except KeyError, e:
|
||||||
|
create_new = True
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.tables[name]
|
||||||
|
create_new = forced or False
|
||||||
|
except KeyError, e:
|
||||||
|
create_new = True
|
||||||
|
|
||||||
|
if create_new == False:
|
||||||
|
return self.tables[name]
|
||||||
|
else:
|
||||||
|
if in_memory == True:
|
||||||
|
new_table = MemoryTable(self, name)
|
||||||
|
else:
|
||||||
|
new_table = DatabaseTable(self, name)
|
||||||
|
|
||||||
|
self.tables[name] = new_table
|
||||||
|
return new_table
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.get_database_table(key)
|
||||||
|
|
||||||
|
def get_database_table(self, name):
|
||||||
|
return self._get_table(name, in_memory=False)
|
||||||
|
|
||||||
|
def get_memory_table(self, name):
|
||||||
|
return self._get_table(name, in_memory=True)
|
||||||
|
|
||||||
|
def query(self, query, params = [], commit=False):
|
||||||
|
print "QUERY: %s" % query
|
||||||
|
print "PARAMS: %s" % repr(params)
|
||||||
|
|
||||||
|
cur = self._get_cursor()
|
||||||
|
cur.execute(query, params)
|
||||||
|
|
||||||
|
if commit == True:
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
return cur
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
self._table_create_nodes()
|
||||||
|
|
||||||
|
class Row(object):
|
||||||
|
def __init__(self, cursor=None, row=None):
|
||||||
|
self._commit_buffer = {}
|
||||||
|
self._data = {}
|
||||||
|
|
||||||
|
if cursor is None and row is None:
|
||||||
|
self._new = True
|
||||||
|
else:
|
||||||
|
self._new = False
|
||||||
|
|
||||||
|
for index, column in enumerate(cursor.description):
|
||||||
|
self._data[column[0]] = row[index]
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._data[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._commit_buffer[key] = value
|
||||||
|
|
||||||
|
def _clear_buffer(self):
|
||||||
|
self._commit_buffer = {}
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
# Commit to database
|
||||||
|
if len(self._commit_buffer) > 0:
|
||||||
|
statement_list = ", ".join("`%s` = ?" % key for key in self._commit_buffer.keys())
|
||||||
|
query = "UPDATE %s SET %s WHERE `id` = %s" % (self._nexus_table, statement_list, self['id']) # Not SQLi-safe!
|
||||||
|
self._nexus_db.query(query, params=self._commit_buffer.values(), commit=True)
|
||||||
|
|
||||||
|
# Update locally
|
||||||
|
for key, value in self._commit_buffer.iteritems():
|
||||||
|
self._data[key] = value
|
||||||
|
|
||||||
|
# Clear out commit buffer
|
||||||
|
self._clear_buffer()
|
||||||
|
|
||||||
|
def rollback(self):
|
||||||
|
self._clear_buffer()
|
||||||
|
|
||||||
|
class Table(object):
|
||||||
|
def __init__(self, database, table_name):
|
||||||
|
# You should never construct this directly!
|
||||||
|
self.db = database
|
||||||
|
self.table = table_name
|
||||||
|
|
||||||
|
def _process_insert(self, value, key=None):
|
||||||
|
if key is not None:
|
||||||
|
value['id'] = key
|
||||||
|
|
||||||
|
column_list = ", ".join("`%s`" % name for name in value._commit_buffer.keys())
|
||||||
|
sub_list = ", ".join("?" for name in value._commit_buffer.keys())
|
||||||
|
query = "INSERT INTO %s (%s) VALUES (%s)" % (self.table, column_list, sub_list) # Not SQLi-safe!
|
||||||
|
|
||||||
|
result = self.db.query(query, params=value._commit_buffer.values(), commit=True)
|
||||||
|
|
||||||
|
value._new = False
|
||||||
|
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
def _try_set(self, key, value, cache):
|
||||||
|
if key in cache:
|
||||||
|
raise TypeError("A row with the given ID already exists. Either edit the existing one, or append a new row using append().")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self._process_insert(value, key)
|
||||||
|
except sqlite3.IntegrityError, e:
|
||||||
|
raise TypeError("A row with the given ID already exists. Either edit the existing one, or append a new row using append().")
|
||||||
|
|
||||||
|
def append(self, value):
|
||||||
|
return self._process_insert(value)
|
||||||
|
|
||||||
|
class DatabaseTable(Table):
|
||||||
|
def __init__(self, database, table_name):
|
||||||
|
Table.__init__(self, database, table_name)
|
||||||
|
self._cache = {}
|
||||||
|
|
||||||
|
def _process_insert(self, value, key=None):
|
||||||
|
rowid = Table._process_insert(self, value, key)
|
||||||
|
self._cache[rowid] = value
|
||||||
|
return rowid
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
try:
|
||||||
|
return self._cache[key]
|
||||||
|
except KeyError, e:
|
||||||
|
result = self.db.query("SELECT * FROM %s WHERE `id` = ?" % self.table, params=(key,))
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
raise KeyError("No row with that ID was found in the table.")
|
||||||
|
else:
|
||||||
|
row = result.fetchone()
|
||||||
|
row._nexus_db = self.db
|
||||||
|
row._nexus_table = self.table
|
||||||
|
row._nexus_type = "database"
|
||||||
|
self._cache[key] = row
|
||||||
|
return row
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._try_set(key, value, self._cache)
|
||||||
|
|
||||||
|
class MemoryTable(Table):
|
||||||
|
def __init__(self, database, table_name):
|
||||||
|
Table.__init__(self, database, table_name)
|
||||||
|
self.data = {}
|
||||||
|
|
||||||
|
result = database.query("SELECT * FROM %s" % table_name) # Not SQLi-safe!
|
||||||
|
|
||||||
|
for row in result:
|
||||||
|
row._nexus_db = database
|
||||||
|
row._nexus_table = table_name
|
||||||
|
row._nexus_type = "memory"
|
||||||
|
self.data[row['id']] = row
|
||||||
|
|
||||||
|
def _process_insert(self, value, key=None):
|
||||||
|
rowid = Table._process_insert(self, value, key)
|
||||||
|
self.data[rowid] = value
|
||||||
|
return rowid
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.data[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._try_set(key, value, self.data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Testing code
|
||||||
|
db = Database()
|
||||||
|
db.setup()
|
||||||
|
table = db.get_database_table("nodes")
|
||||||
|
|
||||||
|
new_row = Row()
|
||||||
|
new_row['uuid'] = "abc"
|
||||||
|
new_row['host'] = "def"
|
||||||
|
new_row['port'] = 123
|
||||||
|
|
||||||
|
#table.append(new_row)
|
||||||
|
table[10] = new_row
|
||||||
|
|
||||||
|
|
||||||
|
#table[1]['uuid'] = "bleep"
|
||||||
|
#table[1]['host'] = "bloop"
|
||||||
|
#table[1].commit()
|
||||||
|
#table[1]['port'] = 1234
|
||||||
|
#table[1].commit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This is a complete mess. Looks like subclassing C extension stuff is a bad idea.
|
||||||
|
|
||||||
|
class Row(sqlite3.Row):
|
||||||
|
def __init__(self, cursor, row):
|
||||||
|
sqlite3.Row.__init__(self, cursor, row)
|
||||||
|
#super(sqlite3.Row, self).__init__(cursor, row) # This segfaults!
|
||||||
|
self._commit_buffer = {}
|
||||||
|
self._commit_data = {}
|
||||||
|
|
||||||
|
# Yes, this will make lookup slower. No real solution for this for now.
|
||||||
|
for key in self.keys():
|
||||||
|
self._commit_data[key] = self[key]
|
||||||
|
|
||||||
|
self.__getitem__ = self._nexus_getitem
|
||||||
|
|
||||||
|
def _nexus_getitem(self, key):
|
||||||
|
# FIXME: Currently, only when using dict access, the data modified through a commit will be accessible.
|
||||||
|
#try:
|
||||||
|
print "GET %s" % key
|
||||||
|
return self._commit_data[key]
|
||||||
|
#except KeyError, e:
|
||||||
|
# #return sqlite3.Row.__get__(self, key)
|
||||||
|
# print super(Row, self).__get__(key)
|
||||||
|
# return super(Row, self).__get__(key)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._commit_buffer[key] = value
|
||||||
|
|
||||||
|
def _clear_buffer(self):
|
||||||
|
self._commit_buffer = {}
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
# Commit to database
|
||||||
|
statement_list = ", ".join("`%s` = ?" % key for key in self._commit_buffer.keys())
|
||||||
|
query = "UPDATE %s SET %s WHERE `id` = %s" % (self._nexus_table, statement_list, self['id']) # Not SQLi-safe!
|
||||||
|
print query
|
||||||
|
print self._commit_buffer.values()
|
||||||
|
#self._nexus_db.query(query, params=self._commit_buffer.values())
|
||||||
|
|
||||||
|
# Update locally
|
||||||
|
for key, value in self._commit_buffer.iteritems():
|
||||||
|
self._commit_data[key] = value
|
||||||
|
|
||||||
|
# Clear out commit buffer
|
||||||
|
self._clear_buffer()
|
||||||
|
|
||||||
|
def rollback(self):
|
||||||
|
self._clear_buffer()
|
||||||
|
"""
|
Binary file not shown.
Loading…
Reference in New Issue