|
|
@ -1,43 +1,83 @@ |
|
|
|
# Copyright (c) 2016, the ElectrumX authors |
|
|
|
# |
|
|
|
# All rights reserved. |
|
|
|
# |
|
|
|
# See the file "LICENCE" for information about the copyright |
|
|
|
# and warranty status of this software. |
|
|
|
|
|
|
|
'''Backend database abstraction. |
|
|
|
|
|
|
|
The abstraction needs to be improved to not heavily penalise LMDB. |
|
|
|
''' |
|
|
|
|
|
|
|
import os |
|
|
|
from functools import partial |
|
|
|
|
|
|
|
from lib.util import subclasses |
|
|
|
|
|
|
|
|
|
|
|
def open_db(name, db_engine): |
|
|
|
'''Returns a database handle.''' |
|
|
|
for db_class in subclasses(Storage): |
|
|
|
if db_class.__name__.lower() == db_engine.lower(): |
|
|
|
db_class.import_module() |
|
|
|
return db_class(name) |
|
|
|
|
|
|
|
raise RuntimeError('unrecognised DB engine "{}"'.format(db_engine)) |
|
|
|
|
|
|
|
|
|
|
|
class Storage(object): |
|
|
|
def __init__(self, name, create_if_missing=False, error_if_exists=False, compression=None): |
|
|
|
if not create_if_missing and not os.path.exists(name): |
|
|
|
raise NoDatabaseException |
|
|
|
'''Abstract base class of the DB backend abstraction.''' |
|
|
|
|
|
|
|
def __init__(self, name): |
|
|
|
self.is_new = not os.path.exists(name) |
|
|
|
self.open(name, create=self.is_new) |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def import_module(cls): |
|
|
|
'''Import the DB engine module.''' |
|
|
|
raise NotImplementedError |
|
|
|
|
|
|
|
def open(self, name, create): |
|
|
|
'''Open an existing database or create a new one.''' |
|
|
|
raise NotImplementedError |
|
|
|
|
|
|
|
def get(self, key): |
|
|
|
raise NotImplementedError() |
|
|
|
raise NotImplementedError |
|
|
|
|
|
|
|
def put(self, key, value): |
|
|
|
raise NotImplementedError() |
|
|
|
raise NotImplementedError |
|
|
|
|
|
|
|
def write_batch(self): |
|
|
|
""" |
|
|
|
Returns a context manager that provides `put` and `delete`. |
|
|
|
Changes should only be committed when the context manager closes without an exception. |
|
|
|
""" |
|
|
|
raise NotImplementedError() |
|
|
|
'''Return a context manager that provides `put` and `delete`. |
|
|
|
|
|
|
|
def iterator(self, prefix=b'', reverse=False): |
|
|
|
""" |
|
|
|
Returns an iterator that yields (key, value) pairs from the database sorted by key. |
|
|
|
If `prefix` is set, only keys starting with `prefix` will be included. |
|
|
|
""" |
|
|
|
raise NotImplementedError() |
|
|
|
Changes should only be committed when the context manager |
|
|
|
closes without an exception. |
|
|
|
''' |
|
|
|
raise NotImplementedError |
|
|
|
|
|
|
|
def iterator(self, prefix=b'', reverse=False): |
|
|
|
'''Return an iterator that yields (key, value) pairs from the |
|
|
|
database sorted by key. |
|
|
|
|
|
|
|
class NoDatabaseException(Exception): |
|
|
|
pass |
|
|
|
If `prefix` is set, only keys starting with `prefix` will be |
|
|
|
included. If `reverse` is True the items are returned in |
|
|
|
reverse order. |
|
|
|
''' |
|
|
|
raise NotImplementedError |
|
|
|
|
|
|
|
|
|
|
|
class LevelDB(Storage): |
|
|
|
def __init__(self, name, create_if_missing=False, error_if_exists=False, compression=None): |
|
|
|
super().__init__(name, create_if_missing, error_if_exists, compression) |
|
|
|
'''LevelDB database engine.''' |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def import_module(cls): |
|
|
|
import plyvel |
|
|
|
self.db = plyvel.DB(name, create_if_missing=create_if_missing, |
|
|
|
error_if_exists=error_if_exists, compression=compression) |
|
|
|
cls.module = plyvel |
|
|
|
|
|
|
|
def open(self, name, create): |
|
|
|
self.db = self.module.DB(name, create_if_missing=create, |
|
|
|
compression=None) |
|
|
|
self.get = self.db.get |
|
|
|
self.put = self.db.put |
|
|
|
self.iterator = self.db.iterator |
|
|
@ -45,25 +85,28 @@ class LevelDB(Storage): |
|
|
|
|
|
|
|
|
|
|
|
class RocksDB(Storage): |
|
|
|
rocksdb = None |
|
|
|
'''RocksDB database engine.''' |
|
|
|
|
|
|
|
def __init__(self, name, create_if_missing=False, error_if_exists=False, compression=None): |
|
|
|
super().__init__(name, create_if_missing, error_if_exists, compression) |
|
|
|
@classmethod |
|
|
|
def import_module(cls): |
|
|
|
import rocksdb |
|
|
|
RocksDB.rocksdb = rocksdb |
|
|
|
if not compression: |
|
|
|
compression = "no" |
|
|
|
compression = getattr(rocksdb.CompressionType, compression + "_compression") |
|
|
|
self.db = rocksdb.DB(name, rocksdb.Options(create_if_missing=create_if_missing, |
|
|
|
compression=compression, |
|
|
|
target_file_size_base=33554432, |
|
|
|
max_open_files=1024)) |
|
|
|
cls.module = rocksdb |
|
|
|
|
|
|
|
def open(self, name, create): |
|
|
|
compression = "no" |
|
|
|
compression = getattr(self.module.CompressionType, |
|
|
|
compression + "_compression") |
|
|
|
options = self.module.Options(create_if_missing=create, |
|
|
|
compression=compression, |
|
|
|
target_file_size_base=33554432, |
|
|
|
max_open_files=1024) |
|
|
|
self.db = self.module.DB(name, options) |
|
|
|
self.get = self.db.get |
|
|
|
self.put = self.db.put |
|
|
|
|
|
|
|
class WriteBatch(object): |
|
|
|
def __init__(self, db): |
|
|
|
self.batch = RocksDB.rocksdb.WriteBatch() |
|
|
|
self.batch = RocksDB.module.WriteBatch() |
|
|
|
self.db = db |
|
|
|
|
|
|
|
def __enter__(self): |
|
|
@ -99,14 +142,17 @@ class RocksDB(Storage): |
|
|
|
|
|
|
|
|
|
|
|
class LMDB(Storage): |
|
|
|
lmdb = None |
|
|
|
'''RocksDB database engine.''' |
|
|
|
|
|
|
|
def __init__(self, name, create_if_missing=False, error_if_exists=False, compression=None): |
|
|
|
super().__init__(name, create_if_missing, error_if_exists, compression) |
|
|
|
@classmethod |
|
|
|
def import_module(cls): |
|
|
|
import lmdb |
|
|
|
LMDB.lmdb = lmdb |
|
|
|
self.env = lmdb.Environment(".", subdir=True, create=create_if_missing, max_dbs=32, map_size=5 * 10 ** 10) |
|
|
|
self.db = self.env.open_db(create=create_if_missing) |
|
|
|
cls.module = lmdb |
|
|
|
|
|
|
|
def open(self, name, create): |
|
|
|
self.env = cls.module.Environment('.', subdir=True, create=create, |
|
|
|
max_dbs=32, map_size=5 * 10 ** 10) |
|
|
|
self.db = self.env.open_db(create=create) |
|
|
|
|
|
|
|
def get(self, key): |
|
|
|
with self.env.begin(db=self.db) as tx: |
|
|
|