CodernityDB python 3
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
__version__ = '0.4.2'
|
||||
__license__ = "Apache 2.0"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from gevent.lock import RLock
|
||||
|
||||
from CodernityDB3.env import cdb_environment
|
||||
|
||||
cdb_environment['mode'] = "gevent"
|
||||
cdb_environment['rlock_obj'] = RLock
|
||||
|
||||
|
||||
# from CodernityDB3.database import Database
|
||||
from CodernityDB3.database_safe_shared import SafeDatabase
|
||||
|
||||
|
||||
class GeventDatabase(SafeDatabase):
|
||||
pass
|
||||
@@ -0,0 +1,229 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from CodernityDB3.env import cdb_environment
|
||||
from CodernityDB3.database import PreconditionsException, RevConflict, Database
|
||||
# from database import Database
|
||||
|
||||
from collections import defaultdict
|
||||
from functools import wraps
|
||||
from types import MethodType
|
||||
|
||||
|
||||
class th_safe_gen:
|
||||
|
||||
def __init__(self, name, gen, l=None):
|
||||
self.lock = l
|
||||
self.__gen = gen
|
||||
self.name = name
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
with self.lock:
|
||||
return next(self.__gen)
|
||||
|
||||
@staticmethod
|
||||
def wrapper(method, index_name, meth_name, l=None):
|
||||
@wraps(method)
|
||||
def _inner(*args, **kwargs):
|
||||
res = method(*args, **kwargs)
|
||||
return th_safe_gen(index_name + "_" + meth_name, res, l)
|
||||
return _inner
|
||||
|
||||
|
||||
def safe_wrapper(method, lock):
|
||||
@wraps(method)
|
||||
def _inner(*args, **kwargs):
|
||||
with lock:
|
||||
return method(*args, **kwargs)
|
||||
return _inner
|
||||
|
||||
|
||||
class SafeDatabase(Database):
|
||||
|
||||
def __init__(self, path, *args, **kwargs):
|
||||
super(SafeDatabase, self).__init__(path, *args, **kwargs)
|
||||
self.indexes_locks = defaultdict(
|
||||
lambda: cdb_environment['rlock_obj']())
|
||||
self.close_open_lock = cdb_environment['rlock_obj']()
|
||||
self.main_lock = cdb_environment['rlock_obj']()
|
||||
self.id_revs = {}
|
||||
|
||||
def __patch_index_gens(self, name):
|
||||
ind = self.indexes_names[name]
|
||||
for c in ('all', 'get_many'):
|
||||
m = getattr(ind, c)
|
||||
if getattr(ind, c + "_orig", None):
|
||||
return
|
||||
m_fixed = th_safe_gen.wrapper(m, name, c, self.indexes_locks[name])
|
||||
setattr(ind, c, m_fixed)
|
||||
setattr(ind, c + '_orig', m)
|
||||
|
||||
def __patch_index_methods(self, name):
|
||||
ind = self.indexes_names[name]
|
||||
lock = self.indexes_locks[name]
|
||||
for curr in dir(ind):
|
||||
meth = getattr(ind, curr)
|
||||
if not curr.startswith('_') and isinstance(meth, MethodType):
|
||||
setattr(ind, curr, safe_wrapper(meth, lock))
|
||||
stor = ind.storage
|
||||
for curr in dir(stor):
|
||||
meth = getattr(stor, curr)
|
||||
if not curr.startswith('_') and isinstance(meth, MethodType):
|
||||
setattr(stor, curr, safe_wrapper(meth, lock))
|
||||
|
||||
def __patch_index(self, name):
|
||||
self.__patch_index_methods(name)
|
||||
self.__patch_index_gens(name)
|
||||
|
||||
def initialize(self, *args, **kwargs):
|
||||
with self.close_open_lock:
|
||||
self.close_open_lock.acquire()
|
||||
res = super(SafeDatabase, self).initialize(*args, **kwargs)
|
||||
for name in self.indexes_names.keys():
|
||||
self.indexes_locks[name] = cdb_environment['rlock_obj']()
|
||||
return res
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
with self.close_open_lock:
|
||||
res = super(SafeDatabase, self).open(*args, **kwargs)
|
||||
for name in self.indexes_names.keys():
|
||||
self.indexes_locks[name] = cdb_environment['rlock_obj']()
|
||||
self.__patch_index(name)
|
||||
return res
|
||||
|
||||
def create(self, *args, **kwargs):
|
||||
with self.close_open_lock:
|
||||
res = super(SafeDatabase, self).create(*args, **kwargs)
|
||||
for name in self.indexes_names.keys():
|
||||
self.indexes_locks[name] = cdb_environment['rlock_obj']()
|
||||
self.__patch_index(name)
|
||||
return res
|
||||
|
||||
def close(self):
|
||||
with self.close_open_lock:
|
||||
return super(SafeDatabase, self).close()
|
||||
|
||||
def destroy(self):
|
||||
with self.close_open_lock:
|
||||
return super(SafeDatabase, self).destroy()
|
||||
|
||||
def add_index(self, *args, **kwargs):
|
||||
with self.main_lock:
|
||||
res = super(SafeDatabase, self).add_index(*args, **kwargs)
|
||||
if self.opened:
|
||||
self.indexes_locks[res] = cdb_environment['rlock_obj']()
|
||||
self.__patch_index(res)
|
||||
return res
|
||||
|
||||
def _single_update_index(self, index, data, db_data, doc_id):
|
||||
with self.indexes_locks[index.name]:
|
||||
super(SafeDatabase, self)._single_update_index(
|
||||
index, data, db_data, doc_id)
|
||||
|
||||
def _single_delete_index(self, index, data, doc_id, old_data):
|
||||
with self.indexes_locks[index.name]:
|
||||
super(SafeDatabase, self)._single_delete_index(
|
||||
index, data, doc_id, old_data)
|
||||
|
||||
def edit_index(self, *args, **kwargs):
|
||||
with self.main_lock:
|
||||
res = super(SafeDatabase, self).edit_index(*args, **kwargs)
|
||||
if self.opened:
|
||||
self.indexes_locks[res] = cdb_environment['rlock_obj']()
|
||||
self.__patch_index(res)
|
||||
return res
|
||||
|
||||
def set_indexes(self, *args, **kwargs):
|
||||
try:
|
||||
self.main_lock.acquire()
|
||||
super(SafeDatabase, self).set_indexes(*args, **kwargs)
|
||||
finally:
|
||||
self.main_lock.release()
|
||||
|
||||
def reindex_index(self, index, *args, **kwargs):
|
||||
if isinstance(index, str):
|
||||
if not index in self.indexes_names:
|
||||
raise PreconditionsException("No index named %s" % index)
|
||||
index = self.indexes_names[index]
|
||||
key = index.name + "reind"
|
||||
self.main_lock.acquire()
|
||||
if key in self.indexes_locks:
|
||||
lock = self.indexes_locks[index.name + "reind"]
|
||||
else:
|
||||
self.indexes_locks[index.name +
|
||||
"reind"] = cdb_environment['rlock_obj']()
|
||||
lock = self.indexes_locks[index.name + "reind"]
|
||||
self.main_lock.release()
|
||||
try:
|
||||
lock.acquire()
|
||||
super(SafeDatabase, self).reindex_index(
|
||||
index, *args, **kwargs)
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def flush(self):
|
||||
try:
|
||||
self.main_lock.acquire()
|
||||
super(SafeDatabase, self).flush()
|
||||
finally:
|
||||
self.main_lock.release()
|
||||
|
||||
def fsync(self):
|
||||
try:
|
||||
self.main_lock.acquire()
|
||||
super(SafeDatabase, self).fsync()
|
||||
finally:
|
||||
self.main_lock.release()
|
||||
|
||||
def _update_id_index(self, _rev, data):
|
||||
with self.indexes_locks['id']:
|
||||
return super(SafeDatabase, self)._update_id_index(_rev, data)
|
||||
|
||||
def _delete_id_index(self, _id, _rev, data):
|
||||
with self.indexes_locks['id']:
|
||||
return super(SafeDatabase, self)._delete_id_index(_id, _rev, data)
|
||||
|
||||
def _update_indexes(self, _rev, data):
|
||||
_id, new_rev, db_data = self._update_id_index(_rev, data)
|
||||
with self.main_lock:
|
||||
self.id_revs[_id] = new_rev
|
||||
for index in self.indexes[1:]:
|
||||
with self.main_lock:
|
||||
curr_rev = self.id_revs.get(_id) # get last _id, _rev
|
||||
if curr_rev != new_rev:
|
||||
break # new update on the way stop current
|
||||
self._single_update_index(index, data, db_data, _id)
|
||||
with self.main_lock:
|
||||
if self.id_revs[_id] == new_rev:
|
||||
del self.id_revs[_id]
|
||||
return _id, new_rev
|
||||
|
||||
def _delete_indexes(self, _id, _rev, data):
|
||||
old_data = self.get('id', _id)
|
||||
if old_data['_rev'] != _rev:
|
||||
raise RevConflict()
|
||||
with self.main_lock:
|
||||
self.id_revs[_id] = _rev
|
||||
for index in self.indexes[1:]:
|
||||
self._single_delete_index(index, data, _id, old_data)
|
||||
self._delete_id_index(_id, _rev, data)
|
||||
with self.main_lock:
|
||||
if self.id_revs[_id] == _rev:
|
||||
del self.id_revs[_id]
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from threading import RLock
|
||||
|
||||
from CodernityDB3.env import cdb_environment
|
||||
|
||||
cdb_environment['mode'] = "threads"
|
||||
cdb_environment['rlock_obj'] = RLock
|
||||
|
||||
from .database import Database
|
||||
|
||||
from functools import wraps
|
||||
from types import FunctionType, MethodType
|
||||
|
||||
from CodernityDB3.database_safe_shared import th_safe_gen
|
||||
|
||||
|
||||
class SuperLock(type):
|
||||
|
||||
@staticmethod
|
||||
def wrapper(f):
|
||||
@wraps(f)
|
||||
def _inner(*args, **kwargs):
|
||||
db = args[0]
|
||||
with db.super_lock:
|
||||
# print '=>', f.__name__, repr(args[1:])
|
||||
res = f(*args, **kwargs)
|
||||
# if db.opened:
|
||||
# db.flush()
|
||||
# print '<=', f.__name__, repr(args[1:])
|
||||
return res
|
||||
return _inner
|
||||
|
||||
def __new__(cls, classname, bases, attr):
|
||||
new_attr = {}
|
||||
for base in bases:
|
||||
for b_attr in dir(base):
|
||||
a = getattr(base, b_attr, None)
|
||||
if isinstance(a, MethodType) and not b_attr.startswith('_'):
|
||||
if b_attr == 'flush' or b_attr == 'flush_indexes':
|
||||
pass
|
||||
else:
|
||||
# setattr(base, b_attr, SuperLock.wrapper(a))
|
||||
new_attr[b_attr] = SuperLock.wrapper(a)
|
||||
for attr_name, attr_value in attr.items():
|
||||
if isinstance(attr_value, FunctionType) and not attr_name.startswith('_'):
|
||||
attr_value = SuperLock.wrapper(attr_value)
|
||||
new_attr[attr_name] = attr_value
|
||||
new_attr['super_lock'] = RLock()
|
||||
return type.__new__(cls, classname, bases, new_attr)
|
||||
|
||||
|
||||
class SuperThreadSafeDatabase(Database, metaclass=SuperLock):
|
||||
"""
|
||||
Thread safe version that always allows single thread to use db.
|
||||
It adds the same lock for all methods, so only one operation can be
|
||||
performed in given time. Completely different implementation
|
||||
than ThreadSafe version (without super word)
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SuperThreadSafeDatabase, self).__init__(*args, **kwargs)
|
||||
|
||||
def __patch_index_gens(self, name):
|
||||
ind = self.indexes_names[name]
|
||||
for c in ('all', 'get_many'):
|
||||
m = getattr(ind, c)
|
||||
if getattr(ind, c + "_orig", None):
|
||||
return
|
||||
m_fixed = th_safe_gen.wrapper(m, name, c, self.super_lock)
|
||||
setattr(ind, c, m_fixed)
|
||||
setattr(ind, c + '_orig', m)
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
res = super(SuperThreadSafeDatabase, self).open(*args, **kwargs)
|
||||
for name in self.indexes_names.keys():
|
||||
self.__patch_index_gens(name)
|
||||
return res
|
||||
|
||||
def create(self, *args, **kwargs):
|
||||
res = super(SuperThreadSafeDatabase, self).create(*args, **kwargs)
|
||||
for name in self.indexes_names.keys():
|
||||
self.__patch_index_gens(name)
|
||||
return res
|
||||
|
||||
def add_index(self, *args, **kwargs):
|
||||
res = super(SuperThreadSafeDatabase, self).add_index(*args, **kwargs)
|
||||
self.__patch_index_gens(res)
|
||||
return res
|
||||
|
||||
def edit_index(self, *args, **kwargs):
|
||||
res = super(SuperThreadSafeDatabase, self).edit_index(*args, **kwargs)
|
||||
self.__patch_index_gens(res)
|
||||
return res
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from threading import RLock
|
||||
|
||||
from CodernityDB3.env import cdb_environment
|
||||
|
||||
cdb_environment['mode'] = "threads"
|
||||
cdb_environment['rlock_obj'] = RLock
|
||||
|
||||
|
||||
from .database_safe_shared import SafeDatabase
|
||||
|
||||
|
||||
class ThreadSafeDatabase(SafeDatabase):
|
||||
"""
|
||||
Thread safe version of CodernityDB that uses several lock objects,
|
||||
on different methods / different indexes etc. It's completely different
|
||||
implementation of locking than SuperThreadSafe one.
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from CodernityDB3.tree_index import TreeBasedIndex
|
||||
import struct
|
||||
import os
|
||||
|
||||
import inspect
|
||||
from functools import wraps
|
||||
import json
|
||||
|
||||
|
||||
class DebugTreeBasedIndex(TreeBasedIndex):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DebugTreeBasedIndex, self).__init__(*args, **kwargs)
|
||||
|
||||
def print_tree(self):
|
||||
print '-----CURRENT TREE-----'
|
||||
print self.root_flag
|
||||
|
||||
if self.root_flag == 'l':
|
||||
print '---ROOT---'
|
||||
self._print_leaf_data(self.data_start)
|
||||
return
|
||||
else:
|
||||
print '---ROOT---'
|
||||
self._print_node_data(self.data_start)
|
||||
nr_of_el, children_flag = self._read_node_nr_of_elements_and_children_flag(
|
||||
self.data_start)
|
||||
nodes = []
|
||||
for index in range(nr_of_el):
|
||||
l_pointer, key, r_pointer = self._read_single_node_key(
|
||||
self.data_start, index)
|
||||
nodes.append(l_pointer)
|
||||
nodes.append(r_pointer)
|
||||
print 'ROOT NODES', nodes
|
||||
while children_flag == 'n':
|
||||
self._print_level(nodes, 'n')
|
||||
new_nodes = []
|
||||
for node in nodes:
|
||||
nr_of_el, children_flag = \
|
||||
self._read_node_nr_of_elements_and_children_flag(node)
|
||||
for index in range(nr_of_el):
|
||||
l_pointer, key, r_pointer = self._read_single_node_key(
|
||||
node, index)
|
||||
new_nodes.append(l_pointer)
|
||||
new_nodes.append(r_pointer)
|
||||
nodes = new_nodes
|
||||
self._print_level(nodes, 'l')
|
||||
|
||||
def _print_level(self, nodes, flag):
|
||||
print '---NEXT LVL---'
|
||||
if flag == 'n':
|
||||
for node in nodes:
|
||||
self._print_node_data(node)
|
||||
elif flag == 'l':
|
||||
for node in nodes:
|
||||
self._print_leaf_data(node)
|
||||
|
||||
def _print_leaf_data(self, leaf_start_position):
|
||||
print 'printing data of leaf at', leaf_start_position
|
||||
nr_of_elements = self._read_leaf_nr_of_elements(leaf_start_position)
|
||||
self.buckets.seek(leaf_start_position)
|
||||
data = self.buckets.read(self.leaf_heading_size +
|
||||
nr_of_elements * self.single_leaf_record_size)
|
||||
leaf = struct.unpack('<' + self.leaf_heading_format +
|
||||
nr_of_elements * self.single_leaf_record_format, data)
|
||||
print leaf
|
||||
print
|
||||
|
||||
def _print_node_data(self, node_start_position):
|
||||
print 'printing data of node at', node_start_position
|
||||
nr_of_elements = self._read_node_nr_of_elements_and_children_flag(
|
||||
node_start_position)[0]
|
||||
self.buckets.seek(node_start_position)
|
||||
data = self.buckets.read(self.node_heading_size + self.pointer_size
|
||||
+ nr_of_elements * (self.key_size + self.pointer_size))
|
||||
node = struct.unpack('<' + self.node_heading_format + self.pointer_format
|
||||
+ nr_of_elements * (
|
||||
self.key_format + self.pointer_format),
|
||||
data)
|
||||
print node
|
||||
print
|
||||
# ------------------>
|
||||
|
||||
|
||||
def database_step_by_step(db_obj, path=None):
|
||||
|
||||
if not path:
|
||||
# ugly for multiplatform support....
|
||||
p = db_obj.path
|
||||
p1 = os.path.split(p)
|
||||
p2 = os.path.split(p1[0])
|
||||
p3 = '_'.join([p2[1], 'operation_logger.log'])
|
||||
path = os.path.join(os.path.split(p2[0])[0], p3)
|
||||
f_obj = open(path, 'wb')
|
||||
|
||||
__stack = [] # inspect.stack() is not working on pytest etc
|
||||
|
||||
def remove_from_stack(name):
|
||||
for i in range(len(__stack)):
|
||||
if __stack[-i] == name:
|
||||
__stack.pop(-i)
|
||||
|
||||
def __dumper(f):
|
||||
@wraps(f)
|
||||
def __inner(*args, **kwargs):
|
||||
funct_name = f.__name__
|
||||
if funct_name == 'count':
|
||||
name = args[0].__name__
|
||||
meth_args = (name,) + args[1:]
|
||||
elif funct_name in ('reindex_index', 'compact_index'):
|
||||
name = args[0].name
|
||||
meth_args = (name,) + args[1:]
|
||||
else:
|
||||
meth_args = args
|
||||
kwargs_copy = kwargs.copy()
|
||||
res = None
|
||||
__stack.append(funct_name)
|
||||
if funct_name == 'insert':
|
||||
try:
|
||||
res = f(*args, **kwargs)
|
||||
except:
|
||||
packed = json.dumps((funct_name,
|
||||
meth_args, kwargs_copy, None))
|
||||
f_obj.write('%s\n' % packed)
|
||||
f_obj.flush()
|
||||
raise
|
||||
else:
|
||||
packed = json.dumps((funct_name,
|
||||
meth_args, kwargs_copy, res))
|
||||
f_obj.write('%s\n' % packed)
|
||||
f_obj.flush()
|
||||
else:
|
||||
if funct_name == 'get':
|
||||
for curr in __stack:
|
||||
if ('delete' in curr or 'update' in curr) and not curr.startswith('test'):
|
||||
remove_from_stack(funct_name)
|
||||
return f(*args, **kwargs)
|
||||
packed = json.dumps((funct_name, meth_args, kwargs_copy))
|
||||
f_obj.write('%s\n' % packed)
|
||||
f_obj.flush()
|
||||
res = f(*args, **kwargs)
|
||||
remove_from_stack(funct_name)
|
||||
return res
|
||||
return __inner
|
||||
|
||||
for meth_name, meth_f in inspect.getmembers(db_obj, predicate=inspect.ismethod):
|
||||
if not meth_name.startswith('_'):
|
||||
setattr(db_obj, meth_name, __dumper(meth_f))
|
||||
|
||||
setattr(db_obj, 'operation_logger', f_obj)
|
||||
|
||||
|
||||
def database_from_steps(db_obj, path):
|
||||
# db_obj.insert=lambda data : insert_for_debug(db_obj, data)
|
||||
with open(path, 'rb') as f_obj:
|
||||
for current in f_obj:
|
||||
line = json.loads(current[:-1])
|
||||
if line[0] == 'count':
|
||||
obj = getattr(db_obj, line[1][0])
|
||||
line[1] = [obj] + line[1][1:]
|
||||
name = line[0]
|
||||
if name == 'insert':
|
||||
try:
|
||||
line[1][0].pop('_rev')
|
||||
except:
|
||||
pass
|
||||
elif name in ('delete', 'update'):
|
||||
el = db_obj.get('id', line[1][0]['_id'])
|
||||
line[1][0]['_rev'] = el['_rev']
|
||||
# print 'FROM STEPS doing', line
|
||||
meth = getattr(db_obj, line[0], None)
|
||||
if not meth:
|
||||
raise Exception("Method = `%s` not found" % line[0])
|
||||
|
||||
meth(*line[1], **line[2])
|
||||
|
||||
|
||||
# def insert_for_debug(self, data):
|
||||
#
|
||||
# _rev = data['_rev']
|
||||
#
|
||||
# if not '_id' in data:
|
||||
# _id = uuid4().hex
|
||||
# else:
|
||||
# _id = data['_id']
|
||||
# data['_id'] = _id
|
||||
# try:
|
||||
# _id = bytes(_id)
|
||||
# except:
|
||||
# raise DatabaseException("`_id` must be valid bytes object")
|
||||
# self._insert_indexes(_id, _rev, data)
|
||||
# ret = {'_id': _id, '_rev': _rev}
|
||||
# data.update(ret)
|
||||
# return ret
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
It's CodernityDB environment.
|
||||
Handles internal informations.'
|
||||
"""
|
||||
|
||||
cdb_environment = {
|
||||
'mode': 'normal'
|
||||
}
|
||||
@@ -0,0 +1,995 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from CodernityDB3.index import (Index,
|
||||
IndexException,
|
||||
DocIdNotFound,
|
||||
ElemNotFound,
|
||||
TryReindexException,
|
||||
IndexPreconditionsException)
|
||||
|
||||
import os
|
||||
import marshal
|
||||
import io
|
||||
import struct
|
||||
import shutil
|
||||
|
||||
from CodernityDB3.storage import IU_Storage, DummyStorage
|
||||
|
||||
from CodernityDB3.env import cdb_environment
|
||||
|
||||
if cdb_environment.get('rlock_obj'):
|
||||
from CodernityDB3 import patch
|
||||
patch.patch_cache_rr(cdb_environment['rlock_obj'])
|
||||
|
||||
from CodernityDB3.rr_cache import cache1lvl
|
||||
|
||||
|
||||
from CodernityDB3.misc import random_hex_32
|
||||
|
||||
try:
|
||||
from CodernityDB3 import __version__
|
||||
except ImportError:
|
||||
from .__init__ import __version__
|
||||
|
||||
|
||||
class IU_HashIndex(Index):
|
||||
"""
|
||||
That class is for Internal Use only, if you want to use HashIndex just subclass the :py:class:`HashIndex` instead this one.
|
||||
|
||||
That design is because main index logic should be always in database not in custom user indexes.
|
||||
"""
|
||||
|
||||
def __init__(self, db_path, name, entry_line_format='<32s{key}IIcI', hash_lim=0xfffff, storage_class=None, key_format='c'):
|
||||
"""
|
||||
The index is capable to solve conflicts by `Separate chaining`
|
||||
:param db_path: database path
|
||||
:type db_path: string
|
||||
:param name: index name
|
||||
:type name: ascii string
|
||||
:param line_format: line format, `key_format` parameter value will replace `{key}` if present.
|
||||
:type line_format: string (32s{key}IIcI by default) {doc_id}{hash_key}{start}{size}{status}{next}
|
||||
:param hash_lim: maximum hash functon results (remember about birthday problem) count from 0
|
||||
:type hash_lim: integer
|
||||
:param storage_class: Storage class by default it will open standard :py:class:`CodernityDB3.storage.Storage` (if string has to be accesible by globals()[storage_class])
|
||||
:type storage_class: class name which will be instance of CodernityDB3.storage.Storage instance or None
|
||||
:param key_format: a index key format
|
||||
"""
|
||||
# Fix types
|
||||
if isinstance(db_path, str):
|
||||
db_path = db_path.encode()
|
||||
if isinstance(name, str):
|
||||
name = name.encode()
|
||||
|
||||
if key_format and '{key}' in entry_line_format:
|
||||
entry_line_format = entry_line_format.replace('{key}', key_format)
|
||||
super(IU_HashIndex, self).__init__(db_path, name)
|
||||
self.hash_lim = hash_lim
|
||||
if not storage_class:
|
||||
storage_class = IU_Storage
|
||||
if storage_class and not isinstance(storage_class, str):
|
||||
storage_class = storage_class.__name__
|
||||
self.storage_class = storage_class
|
||||
self.storage = None
|
||||
|
||||
self.bucket_line_format = "<I"
|
||||
self.bucket_line_size = struct.calcsize(self.bucket_line_format)
|
||||
self.entry_line_format = entry_line_format
|
||||
self.entry_line_size = struct.calcsize(self.entry_line_format)
|
||||
|
||||
cache = cache1lvl(100)
|
||||
self._find_key = cache(self._find_key)
|
||||
self._locate_doc_id = cache(self._locate_doc_id)
|
||||
self.bucket_struct = struct.Struct(self.bucket_line_format)
|
||||
self.entry_struct = struct.Struct(self.entry_line_format)
|
||||
self.data_start = (
|
||||
self.hash_lim + 1) * self.bucket_line_size + self._start_ind + 2
|
||||
|
||||
def _fix_params(self):
|
||||
super(IU_HashIndex, self)._fix_params()
|
||||
self.bucket_line_size = struct.calcsize(self.bucket_line_format)
|
||||
self.entry_line_size = struct.calcsize(self.entry_line_format)
|
||||
self.bucket_struct = struct.Struct(self.bucket_line_format)
|
||||
self.entry_struct = struct.Struct(self.entry_line_format)
|
||||
self.data_start = (
|
||||
self.hash_lim + 1) * self.bucket_line_size + self._start_ind + 2
|
||||
|
||||
def open_index(self):
|
||||
if not os.path.isfile(os.path.join(self.db_path, self.name + '_buck')):
|
||||
raise IndexException("Doesn't exists")
|
||||
self.buckets = io.open(
|
||||
os.path.join(self.db_path, self.name + "_buck"), 'r+b', buffering=0)
|
||||
self._fix_params()
|
||||
self._open_storage()
|
||||
|
||||
def create_index(self):
|
||||
if os.path.isfile(os.path.join(self.db_path, self.name + '_buck')):
|
||||
raise IndexException('Already exists')
|
||||
with io.open(os.path.join(self.db_path, self.name + "_buck"), 'w+b') as f:
|
||||
props = dict(name=self.name,
|
||||
bucket_line_format=self.bucket_line_format,
|
||||
entry_line_format=self.entry_line_format,
|
||||
hash_lim=self.hash_lim,
|
||||
version=self.__version__,
|
||||
storage_class=self.storage_class)
|
||||
f.write(marshal.dumps(props))
|
||||
self.buckets = io.open(
|
||||
os.path.join(self.db_path, self.name + "_buck"), 'r+b', buffering=0)
|
||||
self._create_storage()
|
||||
|
||||
def destroy(self):
|
||||
super(IU_HashIndex, self).destroy()
|
||||
self._clear_cache()
|
||||
|
||||
def _open_storage(self):
|
||||
s = globals()[self.storage_class]
|
||||
if not self.storage:
|
||||
self.storage = s(self.db_path, self.name)
|
||||
self.storage.open()
|
||||
|
||||
def _create_storage(self):
|
||||
s = globals()[self.storage_class]
|
||||
if not self.storage:
|
||||
self.storage = s(self.db_path, self.name)
|
||||
self.storage.create()
|
||||
|
||||
# def close_index(self):
|
||||
# self.buckets.flush()
|
||||
# self.buckets.close()
|
||||
# self.storage.close()
|
||||
# @lfu_cache(100)
|
||||
def _find_key(self, key):
|
||||
"""
|
||||
Find the key position
|
||||
|
||||
:param key: the key to find
|
||||
"""
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
start_position = self._calculate_position(key)
|
||||
self.buckets.seek(start_position)
|
||||
curr_data = self.buckets.read(self.bucket_line_size)
|
||||
if curr_data:
|
||||
location = self.bucket_struct.unpack(curr_data)[0]
|
||||
if not location:
|
||||
return None, None, 0, 0, 'u'
|
||||
found_at, doc_id, l_key, start, size, status, _next = self._locate_key(
|
||||
key, location)
|
||||
if status == 'd': # when first record from many is deleted
|
||||
while True:
|
||||
found_at, doc_id, l_key, start, size, status, _next = self._locate_key(
|
||||
key, _next)
|
||||
if status != 'd':
|
||||
break
|
||||
return doc_id, l_key, start, size, status
|
||||
else:
|
||||
return None, None, 0, 0, 'u'
|
||||
|
||||
def _find_key_many(self, key, limit=1, offset=0):
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
location = None
|
||||
start_position = self._calculate_position(key)
|
||||
self.buckets.seek(start_position)
|
||||
curr_data = self.buckets.read(self.bucket_line_size)
|
||||
if curr_data:
|
||||
location = self.bucket_struct.unpack(curr_data)[0]
|
||||
while offset:
|
||||
if not location:
|
||||
break
|
||||
try:
|
||||
found_at, doc_id, l_key, start, size, status, _next = self._locate_key(
|
||||
key, location)
|
||||
except IndexException:
|
||||
break
|
||||
else:
|
||||
if status != 'd':
|
||||
if l_key == key: # in case of hash function conflicts
|
||||
offset -= 1
|
||||
location = _next
|
||||
while limit:
|
||||
if not location:
|
||||
break
|
||||
try:
|
||||
found_at, doc_id, l_key, start, size, status, _next = self._locate_key(
|
||||
key, location)
|
||||
except IndexException:
|
||||
break
|
||||
else:
|
||||
if status != 'd':
|
||||
if l_key == key: # in case of hash function conflicts
|
||||
yield doc_id, start, size, status
|
||||
limit -= 1
|
||||
location = _next
|
||||
|
||||
def _calculate_position(self, key):
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
return abs(hash(key) & self.hash_lim) * self.bucket_line_size + self._start_ind
|
||||
|
||||
# TODO add cache!
|
||||
def _locate_key(self, key, start):
|
||||
"""
|
||||
Locate position of the key, it will iterate using `next` field in record
|
||||
until required key will be find.
|
||||
|
||||
:param key: the key to locate
|
||||
:param start: position to start from
|
||||
"""
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
location = start
|
||||
while True:
|
||||
self.buckets.seek(location)
|
||||
data = self.buckets.read(self.entry_line_size)
|
||||
# todo, maybe partial read there...
|
||||
try:
|
||||
doc_id, l_key, start, size, status, _next = self.entry_struct.unpack(data)
|
||||
except struct.error:
|
||||
raise ElemNotFound(
|
||||
"Not found") # not found but might be also broken
|
||||
if l_key == key:
|
||||
break
|
||||
else:
|
||||
if not _next:
|
||||
# not found
|
||||
raise ElemNotFound("Not found")
|
||||
else:
|
||||
location = _next # go to next record
|
||||
return location, doc_id, l_key, start, size, status, _next
|
||||
|
||||
# @lfu_cache(100)
|
||||
def _locate_doc_id(self, doc_id, key, start):
|
||||
"""
|
||||
Locate position of the doc_id, it will iterate using `next` field in record
|
||||
until required key will be find.
|
||||
|
||||
:param doc_id: the doc_id to locate
|
||||
:param key: key value
|
||||
:param start: position to start from
|
||||
"""
|
||||
# Fix types
|
||||
if isinstance(doc_id, str):
|
||||
doc_id = doc_id.encode()
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
location = start
|
||||
while True:
|
||||
self.buckets.seek(location)
|
||||
data = self.buckets.read(self.entry_line_size)
|
||||
try:
|
||||
l_doc_id, l_key, start, size, status, _next = self.entry_struct.unpack(data)
|
||||
except:
|
||||
raise DocIdNotFound(
|
||||
"Doc_id '%s' for '%s' not found" % (doc_id, key))
|
||||
if l_doc_id == doc_id and l_key == key: # added for consistency
|
||||
break
|
||||
else:
|
||||
if not _next:
|
||||
# not found
|
||||
raise DocIdNotFound(
|
||||
"Doc_id '%s' for '%s' not found" % (doc_id, key))
|
||||
else:
|
||||
location = _next # go to next record
|
||||
return location, doc_id, l_key, start, size, status, _next
|
||||
|
||||
def _find_place(self, start):
|
||||
"""
|
||||
Find a place to where put the key. It will iterate using `next` field in record, until
|
||||
empty `next` found
|
||||
|
||||
:param start: position to start from
|
||||
"""
|
||||
location = start
|
||||
while True:
|
||||
self.buckets.seek(location)
|
||||
data = self.buckets.read(self.entry_line_size)
|
||||
# todo, maybe partial read there...
|
||||
doc_id, l_key, start, size, status, _next = self.entry_struct.unpack(data)
|
||||
if not _next or status == 'd':
|
||||
return self.buckets.tell() - self.entry_line_size, doc_id, l_key, start, size, status, _next
|
||||
else:
|
||||
location = _next # go to next record
|
||||
|
||||
def update(self, doc_id, key, u_start=0, u_size=0, u_status='o'):
|
||||
# Fix types
|
||||
if isinstance(doc_id, str):
|
||||
doc_id = doc_id.encode()
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
if isinstance(u_status, str):
|
||||
u_status = u_status.encode()
|
||||
|
||||
start_position = self._calculate_position(key)
|
||||
self.buckets.seek(start_position)
|
||||
curr_data = self.buckets.read(self.bucket_line_size)
|
||||
# test if it's unique or not really unique hash
|
||||
if curr_data:
|
||||
location = self.bucket_struct.unpack(curr_data)[0]
|
||||
else:
|
||||
raise ElemNotFound("Location '%s' not found" % doc_id)
|
||||
found_at, _doc_id, _key, start, size, status, _next = self._locate_doc_id(doc_id, key, location)
|
||||
self.buckets.seek(found_at)
|
||||
self.buckets.write(self.entry_struct.pack(doc_id,
|
||||
key,
|
||||
u_start,
|
||||
u_size,
|
||||
u_status,
|
||||
_next))
|
||||
self.flush()
|
||||
self._find_key.delete(key)
|
||||
self._locate_doc_id.delete(doc_id)
|
||||
return True
|
||||
|
||||
def insert(self, doc_id, key, start, size, status='o'):
|
||||
# Fix types
|
||||
if isinstance(doc_id, str):
|
||||
doc_id = doc_id.encode()
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
if isinstance(status, str):
|
||||
status = status.encode()
|
||||
|
||||
start_position = self._calculate_position(key)
|
||||
self.buckets.seek(start_position)
|
||||
curr_data = self.buckets.read(self.bucket_line_size)
|
||||
|
||||
# conflict occurs?
|
||||
if curr_data:
|
||||
location = self.bucket_struct.unpack(curr_data)[0]
|
||||
else:
|
||||
location = 0
|
||||
if location:
|
||||
# last key with that hash
|
||||
try:
|
||||
found_at, _doc_id, _key, _start, _size, _status, _next = self._locate_doc_id(doc_id, key, location)
|
||||
except DocIdNotFound:
|
||||
found_at, _doc_id, _key, _start, _size, _status, _next = self._find_place(location)
|
||||
self.buckets.seek(0, 2)
|
||||
wrote_at = self.buckets.tell()
|
||||
self.buckets.write(self.entry_struct.pack(doc_id,
|
||||
key,
|
||||
start,
|
||||
size,
|
||||
status,
|
||||
_next))
|
||||
# self.flush()
|
||||
self.buckets.seek(found_at)
|
||||
self.buckets.write(self.entry_struct.pack(_doc_id,
|
||||
_key,
|
||||
_start,
|
||||
_size,
|
||||
_status,
|
||||
wrote_at))
|
||||
else:
|
||||
self.buckets.seek(found_at)
|
||||
self.buckets.write(self.entry_struct.pack(doc_id,
|
||||
key,
|
||||
start,
|
||||
size,
|
||||
status,
|
||||
_next))
|
||||
self.flush()
|
||||
self._locate_doc_id.delete(doc_id)
|
||||
self._find_key.delete(_key)
|
||||
# self._find_key.delete(key)
|
||||
# self._locate_key.delete(_key)
|
||||
return True
|
||||
# raise NotImplementedError
|
||||
else:
|
||||
self.buckets.seek(0, 2)
|
||||
wrote_at = self.buckets.tell()
|
||||
|
||||
# check if position is bigger than all hash entries...
|
||||
if wrote_at < self.data_start:
|
||||
self.buckets.seek(self.data_start)
|
||||
wrote_at = self.buckets.tell()
|
||||
|
||||
self.buckets.write(self.entry_struct.pack(doc_id,
|
||||
key,
|
||||
start,
|
||||
size,
|
||||
status,
|
||||
0))
|
||||
# self.flush()
|
||||
self._find_key.delete(key)
|
||||
self.buckets.seek(start_position)
|
||||
self.buckets.write(self.bucket_struct.pack(wrote_at))
|
||||
self.flush()
|
||||
return True
|
||||
|
||||
def get(self, key):
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
return self._find_key(self.make_key(key))
|
||||
|
||||
def get_many(self, key, limit=1, offset=0):
|
||||
return self._find_key_many(self.make_key(key), limit, offset)
|
||||
|
||||
def all(self, limit=-1, offset=0):
|
||||
self.buckets.seek(self.data_start)
|
||||
while offset:
|
||||
curr_data = self.buckets.read(self.entry_line_size)
|
||||
if not curr_data:
|
||||
break
|
||||
try:
|
||||
doc_id, key, start, size, status, _next = self.entry_struct.unpack(curr_data)
|
||||
except IndexException:
|
||||
break
|
||||
else:
|
||||
if status != 'd':
|
||||
offset -= 1
|
||||
while limit:
|
||||
curr_data = self.buckets.read(self.entry_line_size)
|
||||
if not curr_data:
|
||||
break
|
||||
try:
|
||||
doc_id, key, start, size, status, _next = self.entry_struct.unpack(curr_data)
|
||||
except IndexException:
|
||||
break
|
||||
else:
|
||||
if status != 'd':
|
||||
yield doc_id, key, start, size, status
|
||||
limit -= 1
|
||||
|
||||
def _fix_link(self, key, pos_prev, pos_next):
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
# CHECKIT why I need that hack
|
||||
if pos_prev >= self.data_start:
|
||||
self.buckets.seek(pos_prev)
|
||||
data = self.buckets.read(self.entry_line_size)
|
||||
if data:
|
||||
doc_id, l_key, start, size, status, _next = self.entry_struct.unpack(data)
|
||||
self.buckets.seek(pos_prev)
|
||||
self.buckets.write(self.entry_struct.pack(doc_id,
|
||||
l_key,
|
||||
start,
|
||||
size,
|
||||
status,
|
||||
pos_next))
|
||||
self.flush()
|
||||
if pos_next:
|
||||
self.buckets.seek(pos_next)
|
||||
data = self.buckets.read(self.entry_line_size)
|
||||
if data:
|
||||
doc_id, l_key, start, size, status, _next = self.entry_struct.unpack(data)
|
||||
self.buckets.seek(pos_next)
|
||||
self.buckets.write(self.entry_struct.pack(doc_id,
|
||||
l_key,
|
||||
start,
|
||||
size,
|
||||
status,
|
||||
_next))
|
||||
self.flush()
|
||||
return
|
||||
|
||||
def delete(self, doc_id, key, start=0, size=0):
|
||||
# Fix types
|
||||
if isinstance(doc_id, str):
|
||||
doc_id = doc_id.encode()
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
start_position = self._calculate_position(key)
|
||||
self.buckets.seek(start_position)
|
||||
curr_data = self.buckets.read(self.bucket_line_size)
|
||||
if curr_data:
|
||||
location = self.bucket_struct.unpack(curr_data)[0]
|
||||
else:
|
||||
# case happens when trying to delete element with new index key in data
|
||||
# after adding new index to database without reindex
|
||||
raise TryReindexException()
|
||||
found_at, _doc_id, _key, start, size, status, _next = self._locate_doc_id(doc_id, key, location)
|
||||
self.buckets.seek(found_at)
|
||||
self.buckets.write(self.entry_struct.pack(doc_id,
|
||||
key,
|
||||
start,
|
||||
size,
|
||||
'd',
|
||||
_next))
|
||||
self.flush()
|
||||
# self._fix_link(_key, _prev, _next)
|
||||
self._find_key.delete(key)
|
||||
self._locate_doc_id.delete(doc_id)
|
||||
return True
|
||||
|
||||
def compact(self, hash_lim=None):
|
||||
|
||||
if not hash_lim:
|
||||
hash_lim = self.hash_lim
|
||||
|
||||
compact_ind = self.__class__(
|
||||
self.db_path, self.name + '_compact', hash_lim=hash_lim)
|
||||
compact_ind.create_index()
|
||||
|
||||
gen = self.all()
|
||||
while True:
|
||||
try:
|
||||
doc_id, key, start, size, status = next(gen)
|
||||
except StopIteration:
|
||||
break
|
||||
self.storage._f.seek(start)
|
||||
value = self.storage._f.read(size)
|
||||
start_ = compact_ind.storage._f.tell()
|
||||
compact_ind.storage._f.write(value)
|
||||
compact_ind.insert(doc_id, key, start_, size, status)
|
||||
|
||||
compact_ind.close_index()
|
||||
original_name = self.name
|
||||
# os.unlink(os.path.join(self.db_path, self.name + "_buck"))
|
||||
self.close_index()
|
||||
shutil.move(os.path.join(compact_ind.db_path, compact_ind.
|
||||
name + "_buck"), os.path.join(self.db_path, self.name + "_buck"))
|
||||
shutil.move(os.path.join(compact_ind.db_path, compact_ind.
|
||||
name + "_stor"), os.path.join(self.db_path, self.name + "_stor"))
|
||||
# self.name = original_name
|
||||
self.open_index() # reload...
|
||||
self.name = original_name.decode()
|
||||
self._save_params(dict(name=original_name))
|
||||
self._fix_params()
|
||||
self._clear_cache()
|
||||
return True
|
||||
|
||||
def make_key(self, key):
|
||||
return key
|
||||
|
||||
def make_key_value(self, data):
|
||||
return '1', data
|
||||
|
||||
def _clear_cache(self):
|
||||
self._find_key.clear()
|
||||
self._locate_doc_id.clear()
|
||||
|
||||
def close_index(self):
|
||||
super(IU_HashIndex, self).close_index()
|
||||
self._clear_cache()
|
||||
|
||||
|
||||
class IU_UniqueHashIndex(IU_HashIndex):
|
||||
"""
|
||||
Index for *unique* keys! Designed to be a **id** index.
|
||||
|
||||
That class is for Internal Use only, if you want to use UniqueHashIndex just subclass the :py:class:`UniqueHashIndex` instead this one.
|
||||
|
||||
That design is because main index logic should be always in database not in custom user indexes.
|
||||
"""
|
||||
|
||||
def __init__(self, db_path, name, entry_line_format="<32s8sIIcI", *args, **kwargs):
|
||||
# Fix types
|
||||
if isinstance(db_path, str):
|
||||
db_path = db_path.encode()
|
||||
if isinstance(name, str):
|
||||
name = name.encode()
|
||||
|
||||
if 'key' in kwargs:
|
||||
raise IndexPreconditionsException(
|
||||
"UniqueHashIndex doesn't accept key parameter'")
|
||||
super(IU_UniqueHashIndex, self).__init__(db_path, name,
|
||||
entry_line_format, *args, **kwargs)
|
||||
self.create_key = random_hex_32 # : set the function to create random key when no _id given
|
||||
# self.entry_struct=struct.Struct(entry_line_format)
|
||||
|
||||
# @lfu_cache(100)
|
||||
def _find_key(self, key):
|
||||
"""
|
||||
Find the key position
|
||||
|
||||
:param key: the key to find
|
||||
"""
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
start_position = self._calculate_position(key)
|
||||
self.seek = self.buckets.seek(start_position)
|
||||
curr_data = self.buckets.read(self.bucket_line_size)
|
||||
if curr_data:
|
||||
location = self.bucket_struct.unpack(curr_data)[0]
|
||||
found_at, l_key, rev, start, size, status, _next = self._locate_key(
|
||||
key, location)
|
||||
|
||||
# Fix types
|
||||
if isinstance(l_key, bytes):
|
||||
l_key = l_key.decode()
|
||||
if isinstance(rev, bytes):
|
||||
rev = rev.decode()
|
||||
if isinstance(status, bytes):
|
||||
status = status.decode()
|
||||
|
||||
return l_key, rev, start, size, status
|
||||
else:
|
||||
return None, None, 0, 0, 'u'
|
||||
|
||||
def _find_key_many(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _find_place(self, start, key):
|
||||
"""
|
||||
Find a place to where put the key. It will iterate using `next` field in record, until
|
||||
empty `next` found
|
||||
|
||||
:param start: position to start from
|
||||
"""
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
location = start
|
||||
while True:
|
||||
self.buckets.seek(location)
|
||||
data = self.buckets.read(self.entry_line_size)
|
||||
# todo, maybe partial read there...
|
||||
l_key, rev, start, size, status, _next = self.entry_struct.unpack(
|
||||
data)
|
||||
if l_key == key:
|
||||
raise IndexException("The '%s' key already exists" % key)
|
||||
if not _next or status == 'd':
|
||||
return self.buckets.tell() - self.entry_line_size, l_key, rev, start, size, status, _next
|
||||
else:
|
||||
location = _next # go to next record
|
||||
|
||||
# @lfu_cache(100)
|
||||
def _locate_key(self, key, start):
|
||||
"""
|
||||
Locate position of the key, it will iterate using `next` field in record
|
||||
until required key will be find.
|
||||
|
||||
:param key: the key to locate
|
||||
:param start: position to start from
|
||||
"""
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
location = start
|
||||
while True:
|
||||
self.buckets.seek(location)
|
||||
data = self.buckets.read(self.entry_line_size)
|
||||
# todo, maybe partial read there...
|
||||
try:
|
||||
l_key, rev, start, size, status, _next = self.entry_struct.unpack(data)
|
||||
except struct.error:
|
||||
raise ElemNotFound("Location '%s' not found" % key)
|
||||
if l_key == key:
|
||||
break
|
||||
else:
|
||||
if not _next:
|
||||
# not found
|
||||
raise ElemNotFound("Location '%s' not found" % key)
|
||||
else:
|
||||
location = _next # go to next record
|
||||
return self.buckets.tell() - self.entry_line_size, l_key, rev, start, size, status, _next
|
||||
|
||||
def update(self, key, rev, u_start=0, u_size=0, u_status='o'):
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
if isinstance(rev, str):
|
||||
rev = rev.encode()
|
||||
if isinstance(u_status, str):
|
||||
u_status = u_status.encode()
|
||||
|
||||
start_position = self._calculate_position(key)
|
||||
self.buckets.seek(start_position)
|
||||
curr_data = self.buckets.read(self.bucket_line_size)
|
||||
# test if it's unique or not really unique hash
|
||||
|
||||
if curr_data:
|
||||
location = self.bucket_struct.unpack(curr_data)[0]
|
||||
else:
|
||||
raise ElemNotFound("Location '%s' not found" % key)
|
||||
found_at, _key, _rev, start, size, status, _next = self._locate_key(
|
||||
key, location)
|
||||
if u_start == 0:
|
||||
u_start = start
|
||||
if u_size == 0:
|
||||
u_size = size
|
||||
self.buckets.seek(found_at)
|
||||
self.buckets.write(self.entry_struct.pack(key,
|
||||
rev,
|
||||
u_start,
|
||||
u_size,
|
||||
u_status,
|
||||
_next))
|
||||
self.flush()
|
||||
self._find_key.delete(key)
|
||||
return True
|
||||
|
||||
def insert(self, key, rev, start, size, status='o'):
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
if isinstance(rev, str):
|
||||
rev = rev.encode()
|
||||
if isinstance(status, str):
|
||||
status = status.encode()
|
||||
|
||||
start_position = self._calculate_position(key)
|
||||
self.buckets.seek(start_position)
|
||||
curr_data = self.buckets.read(self.bucket_line_size)
|
||||
|
||||
# conflict occurs?
|
||||
if curr_data:
|
||||
location = self.bucket_struct.unpack(curr_data)[0]
|
||||
else:
|
||||
location = 0
|
||||
if location:
|
||||
# last key with that hash
|
||||
found_at, _key, _rev, _start, _size, _status, _next = self._find_place(
|
||||
location, key)
|
||||
self.buckets.seek(0, 2)
|
||||
wrote_at = self.buckets.tell()
|
||||
|
||||
# check if position is bigger than all hash entries...
|
||||
if wrote_at < self.data_start:
|
||||
self.buckets.seek(self.data_start)
|
||||
wrote_at = self.buckets.tell()
|
||||
|
||||
self.buckets.write(self.entry_struct.pack(key,
|
||||
rev,
|
||||
start,
|
||||
size,
|
||||
status,
|
||||
_next))
|
||||
|
||||
# self.flush()
|
||||
self.buckets.seek(found_at)
|
||||
self.buckets.write(self.entry_struct.pack(_key,
|
||||
_rev,
|
||||
_start,
|
||||
_size,
|
||||
_status,
|
||||
wrote_at))
|
||||
self.flush()
|
||||
self._find_key.delete(_key)
|
||||
# self._locate_key.delete(_key)
|
||||
return True
|
||||
# raise NotImplementedError
|
||||
else:
|
||||
self.buckets.seek(0, 2)
|
||||
wrote_at = self.buckets.tell()
|
||||
|
||||
# check if position is bigger than all hash entries...
|
||||
if wrote_at < self.data_start:
|
||||
self.buckets.seek(self.data_start)
|
||||
wrote_at = self.buckets.tell()
|
||||
|
||||
self.buckets.write(self.entry_struct.pack(key,
|
||||
rev,
|
||||
start,
|
||||
size,
|
||||
status,
|
||||
0))
|
||||
|
||||
self.buckets.seek(start_position)
|
||||
self.buckets.write(self.bucket_struct.pack(wrote_at))
|
||||
self.flush()
|
||||
self._find_key.delete(key)
|
||||
return True
|
||||
|
||||
def all(self, limit=-1, offset=0):
|
||||
self.buckets.seek(self.data_start)
|
||||
while offset:
|
||||
curr_data = self.buckets.read(self.entry_line_size)
|
||||
if not curr_data:
|
||||
break
|
||||
try:
|
||||
doc_id, rev, start, size, status, next = self.entry_struct.unpack(curr_data)
|
||||
except IndexException:
|
||||
break
|
||||
else:
|
||||
if status != 'd':
|
||||
offset -= 1
|
||||
|
||||
while limit:
|
||||
curr_data = self.buckets.read(self.entry_line_size)
|
||||
if not curr_data:
|
||||
break
|
||||
try:
|
||||
doc_id, rev, start, size, status, next = self.entry_struct.unpack(curr_data)
|
||||
except IndexException:
|
||||
break
|
||||
else:
|
||||
if status != 'd':
|
||||
yield doc_id, rev, start, size, status
|
||||
limit -= 1
|
||||
|
||||
def get_many(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete(self, key, start=0, size=0):
|
||||
# Fix types
|
||||
if isinstance(key, str):
|
||||
key = key.encode()
|
||||
|
||||
self.update(key, '00000000', start, size, 'd')
|
||||
|
||||
def make_key_value(self, data):
|
||||
_id = data['_id']
|
||||
try:
|
||||
_id = data['_id'].encode()
|
||||
except:
|
||||
raise IndexPreconditionsException(
|
||||
"_id must be valid string/bytes object")
|
||||
if len(_id) != 32:
|
||||
raise IndexPreconditionsException("Invalid _id lenght")
|
||||
del data['_id']
|
||||
del data['_rev']
|
||||
return _id, data
|
||||
|
||||
def destroy(self):
|
||||
Index.destroy(self)
|
||||
self._clear_cache()
|
||||
|
||||
def _clear_cache(self):
|
||||
self._find_key.clear()
|
||||
|
||||
def insert_with_storage(self, _id, _rev, value):
|
||||
# Fix types
|
||||
if isinstance(_id, str):
|
||||
_id = _id.encode()
|
||||
if isinstance(_rev, str):
|
||||
_rev = _rev.encode()
|
||||
if value:
|
||||
start, size = self.storage.insert(value)
|
||||
else:
|
||||
start = 1
|
||||
size = 0
|
||||
return self.insert(_id, _rev, start, size)
|
||||
|
||||
def update_with_storage(self, _id, _rev, value):
|
||||
# Fix types
|
||||
if isinstance(_id, str):
|
||||
_id = _id.encode()
|
||||
if isinstance(_rev, str):
|
||||
_rev = _rev.encode()
|
||||
|
||||
if value:
|
||||
start, size = self.storage.insert(value)
|
||||
else:
|
||||
start = 1
|
||||
size = 0
|
||||
return self.update(_id, _rev, start, size)
|
||||
|
||||
|
||||
class DummyHashIndex(IU_HashIndex):
|
||||
def __init__(self, db_path, name, entry_line_format="<32s4sIIcI", *args, **kwargs):
|
||||
super(DummyHashIndex, self).__init__(db_path, name,
|
||||
entry_line_format, *args, **kwargs)
|
||||
self.create_key = random_hex_32 # : set the function to create random key when no _id given
|
||||
# self.entry_struct=struct.Struct(entry_line_format)
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
def insert(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
def all(self, *args, **kwargs):
|
||||
raise StopIteration
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
raise ElemNotFound
|
||||
|
||||
def get_many(self, *args, **kwargs):
|
||||
raise StopIteration
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def make_key_value(self, data):
|
||||
return '1', {'_': 1}
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def _clear_cache(self):
|
||||
pass
|
||||
|
||||
def _open_storage(self):
|
||||
if not self.storage:
|
||||
self.storage = DummyStorage()
|
||||
self.storage.open()
|
||||
|
||||
def _create_storage(self):
|
||||
if not self.storage:
|
||||
self.storage = DummyStorage()
|
||||
self.storage.create()
|
||||
|
||||
|
||||
class IU_MultiHashIndex(IU_HashIndex):
|
||||
"""
|
||||
Class that allows to index more than one key per database record.
|
||||
|
||||
It operates very well on GET/INSERT. It's not optimized for
|
||||
UPDATE operations (will always readd everything)
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(IU_MultiHashIndex, self).__init__(*args, **kwargs)
|
||||
|
||||
def insert(self, doc_id, key, start, size, status='o'):
|
||||
if isinstance(key, (list, tuple)):
|
||||
key = set(key)
|
||||
elif not isinstance(key, set):
|
||||
key = set([key])
|
||||
ins = super(IU_MultiHashIndex, self).insert
|
||||
for curr_key in key:
|
||||
ins(doc_id, curr_key, start, size, status)
|
||||
return True
|
||||
|
||||
def update(self, doc_id, key, u_start, u_size, u_status='o'):
|
||||
if isinstance(key, (list, tuple)):
|
||||
key = set(key)
|
||||
elif not isinstance(key, set):
|
||||
key = set([key])
|
||||
upd = super(IU_MultiHashIndex, self).update
|
||||
for curr_key in key:
|
||||
upd(doc_id, curr_key, u_start, u_size, u_status)
|
||||
|
||||
def delete(self, doc_id, key, start=0, size=0):
|
||||
if isinstance(key, (list, tuple)):
|
||||
key = set(key)
|
||||
elif not isinstance(key, set):
|
||||
key = set([key])
|
||||
delete = super(IU_MultiHashIndex, self).delete
|
||||
for curr_key in key:
|
||||
delete(doc_id, curr_key, start, size)
|
||||
|
||||
def get(self, key):
|
||||
return super(IU_MultiHashIndex, self).get(key)
|
||||
|
||||
def make_key_value(self, data):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
# classes for public use, done in this way because of
|
||||
# generation static files with indexes (_index directory)
|
||||
|
||||
|
||||
class HashIndex(IU_HashIndex):
|
||||
"""
|
||||
That class is designed to be used in custom indexes.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class UniqueHashIndex(IU_UniqueHashIndex):
|
||||
"""
|
||||
That class is designed to be used in custom indexes. It's designed to be **id** index.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MultiHashIndex(IU_MultiHashIndex):
|
||||
"""
|
||||
That class is designed to be used in custom indexes.
|
||||
"""
|
||||
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import os
|
||||
import marshal
|
||||
|
||||
import struct
|
||||
import shutil
|
||||
|
||||
from CodernityDB3.storage import IU_Storage, DummyStorage
|
||||
|
||||
try:
|
||||
from CodernityDB3 import __version__
|
||||
except ImportError:
|
||||
from .__init__ import __version__
|
||||
|
||||
|
||||
import io
|
||||
|
||||
|
||||
class IndexException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class IndexNotFoundException(IndexException):
|
||||
pass
|
||||
|
||||
|
||||
class ReindexException(IndexException):
|
||||
pass
|
||||
|
||||
|
||||
class TryReindexException(ReindexException):
|
||||
pass
|
||||
|
||||
|
||||
class ElemNotFound(IndexException):
|
||||
pass
|
||||
|
||||
|
||||
class DocIdNotFound(ElemNotFound):
|
||||
pass
|
||||
|
||||
|
||||
class IndexConflict(IndexException):
|
||||
pass
|
||||
|
||||
|
||||
class IndexPreconditionsException(IndexException):
|
||||
pass
|
||||
|
||||
|
||||
class Index(object):
|
||||
|
||||
__version__ = __version__
|
||||
|
||||
custom_header = "" # : use it for imports required by your index
|
||||
|
||||
def __init__(self,
|
||||
db_path,
|
||||
name):
|
||||
if isinstance(name, bytes):
|
||||
name = name.decode()
|
||||
if isinstance(db_path, bytes):
|
||||
db_path = db_path.decode()
|
||||
|
||||
self.name = name
|
||||
self._start_ind = 500
|
||||
self.db_path = db_path
|
||||
|
||||
def open_index(self):
|
||||
if not os.path.isfile(os.path.join(self.db_path, self.name + '_buck')):
|
||||
raise IndexException("Doesn't exists")
|
||||
self.buckets = io.open(
|
||||
os.path.join(self.db_path, self.name + "_buck"), 'r+b', buffering=0)
|
||||
self._fix_params()
|
||||
self._open_storage()
|
||||
|
||||
def _close(self):
|
||||
self.buckets.close()
|
||||
self.storage.close()
|
||||
|
||||
def close_index(self):
|
||||
self.flush()
|
||||
self.fsync()
|
||||
self._close()
|
||||
|
||||
def create_index(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _fix_params(self):
|
||||
self.buckets.seek(0)
|
||||
props = marshal.loads(self.buckets.read(self._start_ind))
|
||||
for k, v in props.items():
|
||||
self.__dict__[k] = v
|
||||
self.buckets.seek(0, 2)
|
||||
|
||||
def _save_params(self, in_params={}):
|
||||
self.buckets.seek(0)
|
||||
props = marshal.loads(self.buckets.read(self._start_ind))
|
||||
props.update(in_params)
|
||||
self.buckets.seek(0)
|
||||
data = marshal.dumps(props)
|
||||
if len(data) > self._start_ind:
|
||||
raise IndexException("To big props")
|
||||
self.buckets.write(data)
|
||||
self.flush()
|
||||
self.buckets.seek(0, 2)
|
||||
self.__dict__.update(props)
|
||||
|
||||
def _open_storage(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def _create_storage(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def _destroy_storage(self, *args, **kwargs):
|
||||
self.storage.destroy()
|
||||
|
||||
def _find_key(self, key):
|
||||
raise NotImplementedError()
|
||||
|
||||
def update(self, doc_id, key, start, size):
|
||||
raise NotImplementedError()
|
||||
|
||||
def insert(self, doc_id, key, start, size):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get(self, key):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_many(self, key, start_from=None, limit=0):
|
||||
raise NotImplementedError()
|
||||
|
||||
def all(self, start_pos):
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete(self, key, start, size):
|
||||
raise NotImplementedError()
|
||||
|
||||
def make_key_value(self, data):
|
||||
raise NotImplementedError()
|
||||
|
||||
def make_key(self, data):
|
||||
raise NotImplementedError()
|
||||
|
||||
def compact(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def destroy(self, *args, **kwargs):
|
||||
self._close()
|
||||
bucket_file = os.path.join(self.db_path, self.name + '_buck')
|
||||
os.unlink(bucket_file)
|
||||
self._destroy_storage()
|
||||
self._find_key.clear()
|
||||
|
||||
def flush(self):
|
||||
try:
|
||||
self.buckets.flush()
|
||||
self.storage.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
def fsync(self):
|
||||
try:
|
||||
os.fsync(self.buckets.fileno())
|
||||
self.storage.fsync()
|
||||
except:
|
||||
pass
|
||||
|
||||
def update_with_storage(self, doc_id, key, value):
|
||||
if value:
|
||||
start, size = self.storage.insert(value)
|
||||
else:
|
||||
start = 1
|
||||
size = 0
|
||||
return self.update(doc_id, key, start, size)
|
||||
|
||||
def insert_with_storage(self, doc_id, key, value):
|
||||
if value is not None:
|
||||
start, size = self.storage.insert(value)
|
||||
else:
|
||||
start = 1
|
||||
size = 0
|
||||
return self.insert(doc_id, key, start, size)
|
||||
@@ -0,0 +1,645 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import re
|
||||
import tokenize
|
||||
import token
|
||||
import uuid
|
||||
|
||||
|
||||
class IndexCreatorException(Exception):
|
||||
def __init__(self, ex, line=None):
|
||||
self.ex = ex
|
||||
self.line = line
|
||||
|
||||
def __str__(self):
|
||||
if self.line:
|
||||
return repr(self.ex + "(in line: %d)" % self.line)
|
||||
return repr(self.ex)
|
||||
|
||||
|
||||
class IndexCreatorFunctionException(IndexCreatorException):
|
||||
pass
|
||||
|
||||
|
||||
class IndexCreatorValueException(IndexCreatorException):
|
||||
pass
|
||||
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def parse(self, data, name=None):
|
||||
if not name:
|
||||
self.name = "_" + uuid.uuid4().hex
|
||||
else:
|
||||
self.name = name
|
||||
|
||||
self.ind = 0
|
||||
self.stage = 0
|
||||
self.logic = ['and', 'or', 'in']
|
||||
self.logic2 = ['&', '|']
|
||||
self.allowed_props = {'TreeBasedIndex': ['type', 'name', 'key_format', 'node_capacity', 'pointer_format', 'meta_format'],
|
||||
'HashIndex': ['type', 'name', 'key_format', 'hash_lim', 'entry_line_format'],
|
||||
'MultiHashIndex': ['type', 'name', 'key_format', 'hash_lim', 'entry_line_format'],
|
||||
'MultiTreeBasedIndex': ['type', 'name', 'key_format', 'node_capacity', 'pointer_format', 'meta_format']
|
||||
}
|
||||
self.funcs = {'md5': (['md5'], ['.digest()']),
|
||||
'len': (['len'], []),
|
||||
'str': (['str'], []),
|
||||
'fix_r': (['self.fix_r'], []),
|
||||
'prefix': (['self.prefix'], []),
|
||||
'infix': (['self.infix'], []),
|
||||
'suffix': (['self.suffix'], [])
|
||||
}
|
||||
self.handle_int_imports = {'infix': "from itertools import izip\n"}
|
||||
|
||||
self.funcs_with_body = {'fix_r':
|
||||
(""" def fix_r(self,s,l):
|
||||
e = len(s)
|
||||
if e == l:
|
||||
return s
|
||||
elif e > l:
|
||||
return s[:l]
|
||||
else:
|
||||
return s.rjust(l,'_')\n""", False),
|
||||
'prefix':
|
||||
(""" def prefix(self,s,m,l,f):
|
||||
t = len(s)
|
||||
if m < 1:
|
||||
m = 1
|
||||
o = set()
|
||||
if t > l:
|
||||
s = s[:l]
|
||||
t = l
|
||||
while m <= t:
|
||||
o.add(s.rjust(f,'_'))
|
||||
s = s[:-1]
|
||||
t -= 1
|
||||
return o\n""", False),
|
||||
'suffix':
|
||||
(""" def suffix(self,s,m,l,f):
|
||||
t = len(s)
|
||||
if m < 1:
|
||||
m = 1
|
||||
o = set()
|
||||
if t > l:
|
||||
s = s[t-l:]
|
||||
t = len(s)
|
||||
while m <= t:
|
||||
o.add(s.rjust(f,'_'))
|
||||
s = s[1:]
|
||||
t -= 1
|
||||
return o\n""", False),
|
||||
'infix':
|
||||
(""" def infix(self,s,m,l,f):
|
||||
t = len(s)
|
||||
o = set()
|
||||
for x in xrange(m - 1, l):
|
||||
t = (s, )
|
||||
for y in xrange(0, x):
|
||||
t += (s[y + 1:],)
|
||||
o.update(set(''.join(x).rjust(f, '_').lower() for x in izip(*t)))
|
||||
return o\n""", False)}
|
||||
self.none = ['None', 'none', 'null']
|
||||
self.props_assign = ['=', ':']
|
||||
self.all_adj_num_comp = {token.NUMBER: (
|
||||
token.NUMBER, token.NAME, '-', '('),
|
||||
token.NAME: (token.NUMBER, token.NAME, '-', '('),
|
||||
')': (token.NUMBER, token.NAME, '-', '(')
|
||||
}
|
||||
|
||||
self.all_adj_num_op = {token.NUMBER: (token.NUMBER, token.NAME, '('),
|
||||
token.NAME: (token.NUMBER, token.NAME, '('),
|
||||
')': (token.NUMBER, token.NAME, '(')
|
||||
}
|
||||
self.allowed_adjacent = {
|
||||
"<=": self.all_adj_num_comp,
|
||||
">=": self.all_adj_num_comp,
|
||||
">": self.all_adj_num_comp,
|
||||
"<": self.all_adj_num_comp,
|
||||
|
||||
"==": {token.NUMBER: (token.NUMBER, token.NAME, '('),
|
||||
token.NAME: (token.NUMBER, token.NAME, token.STRING, '('),
|
||||
token.STRING: (token.NAME, token.STRING, '('),
|
||||
')': (token.NUMBER, token.NAME, token.STRING, '('),
|
||||
']': (token.NUMBER, token.NAME, token.STRING, '(')
|
||||
},
|
||||
|
||||
"+": {token.NUMBER: (token.NUMBER, token.NAME, '('),
|
||||
token.NAME: (token.NUMBER, token.NAME, token.STRING, '('),
|
||||
token.STRING: (token.NAME, token.STRING, '('),
|
||||
')': (token.NUMBER, token.NAME, token.STRING, '('),
|
||||
']': (token.NUMBER, token.NAME, token.STRING, '(')
|
||||
},
|
||||
|
||||
"-": {token.NUMBER: (token.NUMBER, token.NAME, '('),
|
||||
token.NAME: (token.NUMBER, token.NAME, '('),
|
||||
')': (token.NUMBER, token.NAME, '('),
|
||||
'<': (token.NUMBER, token.NAME, '('),
|
||||
'>': (token.NUMBER, token.NAME, '('),
|
||||
'<=': (token.NUMBER, token.NAME, '('),
|
||||
'>=': (token.NUMBER, token.NAME, '('),
|
||||
'==': (token.NUMBER, token.NAME, '('),
|
||||
']': (token.NUMBER, token.NAME, '(')
|
||||
},
|
||||
"*": self.all_adj_num_op,
|
||||
"/": self.all_adj_num_op,
|
||||
"%": self.all_adj_num_op,
|
||||
",": {token.NUMBER: (token.NUMBER, token.NAME, token.STRING, '{', '[', '('),
|
||||
token.NAME: (token.NUMBER, token.NAME, token.STRING, '(', '{', '['),
|
||||
token.STRING: (token.NAME, token.STRING, token.NUMBER, '(', '{', '['),
|
||||
')': (token.NUMBER, token.NAME, token.STRING, '(', '{', '['),
|
||||
']': (token.NUMBER, token.NAME, token.STRING, '(', '{', '['),
|
||||
'}': (token.NUMBER, token.NAME, token.STRING, '(', '{', '[')
|
||||
}
|
||||
}
|
||||
|
||||
def is_num(s):
|
||||
m = re.search('[^0-9*()+\-\s/]+', s)
|
||||
return not m
|
||||
|
||||
def is_string(s):
|
||||
m = re.search('\s*(?P<a>[\'\"]+).*?(?P=a)\s*', s)
|
||||
return m
|
||||
data = re.split('make_key_value\:', data)
|
||||
|
||||
if len(data) < 2:
|
||||
raise IndexCreatorFunctionException(
|
||||
"Couldn't find a definition of make_key_value function!\n")
|
||||
|
||||
spl1 = re.split('make_key\:', data[0])
|
||||
spl2 = re.split('make_key\:', data[1])
|
||||
|
||||
self.funcs_rev = False
|
||||
|
||||
if len(spl1) > 1:
|
||||
data = [spl1[0]] + [data[1]] + [spl1[1]]
|
||||
self.funcs_rev = True
|
||||
elif len(spl2) > 1:
|
||||
data = [data[0]] + spl2
|
||||
else:
|
||||
data.append("key")
|
||||
|
||||
if data[1] == re.search('\s*', data[1], re.S | re.M).group(0):
|
||||
raise IndexCreatorFunctionException("Empty function body ",
|
||||
len(re.split('\n', data[0])) + (len(re.split('\n', data[2])) if self.funcs_rev else 1) - 1)
|
||||
if data[2] == re.search('\s*', data[2], re.S | re.M).group(0):
|
||||
raise IndexCreatorFunctionException("Empty function body ",
|
||||
len(re.split('\n', data[0])) + (1 if self.funcs_rev else len(re.split('\n', data[1]))) - 1)
|
||||
if data[0] == re.search('\s*', data[0], re.S | re.M).group(0):
|
||||
raise IndexCreatorValueException("You didn't set any properity or you set them not at the begining of the code\n")
|
||||
|
||||
data = [re.split(
|
||||
'\n', data[0]), re.split('\n', data[1]), re.split('\n', data[2])]
|
||||
self.cnt_lines = (len(data[0]), len(data[1]), len(data[2]))
|
||||
ind = 0
|
||||
self.predata = data
|
||||
self.data = [[], [], []]
|
||||
for i, v in enumerate(self.predata[0]):
|
||||
for k, w in enumerate(self.predata[0][i]):
|
||||
if self.predata[0][i][k] in self.props_assign:
|
||||
if not is_num(self.predata[0][i][k + 1:]) and self.predata[0][i].strip()[:4] != 'type' and self.predata[0][i].strip()[:4] != 'name':
|
||||
s = self.predata[0][i][k + 1:]
|
||||
self.predata[0][i] = self.predata[0][i][:k + 1]
|
||||
|
||||
m = re.search('\s+', s.strip())
|
||||
if not is_string(s) and not m:
|
||||
s = "'" + s.strip() + "'"
|
||||
self.predata[0][i] += s
|
||||
break
|
||||
|
||||
for n, i in enumerate(self.predata):
|
||||
for k in i:
|
||||
k = k.strip()
|
||||
if k:
|
||||
self.data[ind].append(k)
|
||||
self.check_enclosures(k, n)
|
||||
ind += 1
|
||||
|
||||
return self.parse_ex()
|
||||
|
||||
def readline(self, stage):
|
||||
def foo():
|
||||
if len(self.data[stage]) <= self.ind:
|
||||
self.ind = 0
|
||||
return ""
|
||||
else:
|
||||
self.ind += 1
|
||||
return self.data[stage][self.ind - 1]
|
||||
return foo
|
||||
|
||||
def add(self, l, i):
|
||||
def add_aux(*args):
|
||||
# print args,self.ind
|
||||
if len(l[i]) < self.ind:
|
||||
l[i].append([])
|
||||
l[i][self.ind - 1].append(args)
|
||||
return add_aux
|
||||
|
||||
def parse_ex(self):
|
||||
self.index_name = ""
|
||||
self.index_type = ""
|
||||
self.curLine = -1
|
||||
self.con = -1
|
||||
self.brackets = -1
|
||||
self.curFunc = None
|
||||
self.colons = 0
|
||||
self.line_cons = ([], [], [])
|
||||
self.pre_tokens = ([], [], [])
|
||||
self.known_dicts_in_mkv = []
|
||||
self.prop_name = True
|
||||
self.prop_assign = False
|
||||
self.is_one_arg_enough = False
|
||||
self.funcs_stack = []
|
||||
self.last_line = [-1, -1, -1]
|
||||
self.props_set = []
|
||||
self.custom_header = set()
|
||||
|
||||
self.tokens = []
|
||||
self.tokens_head = ['# %s\n' % self.name, 'class %s(' % self.name, '):\n', ' def __init__(self, *args, **kwargs): ']
|
||||
|
||||
for i in range(3):
|
||||
tokenize.tokenize(self.readline(i), self.add(self.pre_tokens, i))
|
||||
# tokenize treats some keyword not in the right way, thats why we
|
||||
# have to change some of them
|
||||
for nk, k in enumerate(self.pre_tokens[i]):
|
||||
for na, a in enumerate(k):
|
||||
if a[0] == token.NAME and a[1] in self.logic:
|
||||
self.pre_tokens[i][nk][
|
||||
na] = (token.OP, a[1], a[2], a[3], a[4])
|
||||
|
||||
for i in self.pre_tokens[1]:
|
||||
self.line_cons[1].append(self.check_colons(i, 1))
|
||||
self.check_adjacents(i, 1)
|
||||
if self.check_for_2nd_arg(i) == -1 and not self.is_one_arg_enough:
|
||||
raise IndexCreatorValueException("No 2nd value to return (did u forget about ',None'?", self.cnt_line_nr(i[0][4], 1))
|
||||
self.is_one_arg_enough = False
|
||||
|
||||
for i in self.pre_tokens[2]:
|
||||
self.line_cons[2].append(self.check_colons(i, 2))
|
||||
self.check_adjacents(i, 2)
|
||||
|
||||
for i in self.pre_tokens[0]:
|
||||
self.handle_prop_line(i)
|
||||
|
||||
self.cur_brackets = 0
|
||||
self.tokens += ['\n super(%s, self).__init__(*args, **kwargs)\n def make_key_value(self, data): ' % self.name]
|
||||
|
||||
for i in self.pre_tokens[1]:
|
||||
for k in i:
|
||||
self.handle_make_value(*k)
|
||||
|
||||
self.curLine = -1
|
||||
self.con = -1
|
||||
self.cur_brackets = 0
|
||||
self.tokens += ['\n def make_key(self, key):']
|
||||
|
||||
for i in self.pre_tokens[2]:
|
||||
for k in i:
|
||||
self.handle_make_key(*k)
|
||||
|
||||
if self.index_type == "":
|
||||
raise IndexCreatorValueException("Missing index type definition\n")
|
||||
if self.index_name == "":
|
||||
raise IndexCreatorValueException("Missing index name\n")
|
||||
|
||||
self.tokens_head[0] = "# " + self.index_name + "\n" + \
|
||||
self.tokens_head[0]
|
||||
|
||||
for i in self.funcs_with_body:
|
||||
if self.funcs_with_body[i][1]:
|
||||
self.tokens_head.insert(4, self.funcs_with_body[i][0])
|
||||
|
||||
if None in self.custom_header:
|
||||
self.custom_header.remove(None)
|
||||
if self.custom_header:
|
||||
s = ' custom_header = """'
|
||||
for i in self.custom_header:
|
||||
s += i
|
||||
s += '"""\n'
|
||||
self.tokens_head.insert(4, s)
|
||||
|
||||
if self.index_type in self.allowed_props:
|
||||
for i in self.props_set:
|
||||
if i not in self.allowed_props[self.index_type]:
|
||||
raise IndexCreatorValueException("Properity %s is not allowed for index type: %s" % (i, self.index_type))
|
||||
|
||||
# print "".join(self.tokens_head)
|
||||
# print "----------"
|
||||
# print (" ".join(self.tokens))
|
||||
return "".join(self.custom_header), "".join(self.tokens_head) + (" ".join(self.tokens))
|
||||
|
||||
# has to be run BEFORE tokenize
|
||||
def check_enclosures(self, d, st):
|
||||
encs = []
|
||||
contr = {'(': ')', '{': '}', '[': ']', "'": "'", '"': '"'}
|
||||
ends = [')', '}', ']', "'", '"']
|
||||
for i in d:
|
||||
if len(encs) > 0 and encs[-1] in ['"', "'"]:
|
||||
if encs[-1] == i:
|
||||
del encs[-1]
|
||||
elif i in contr:
|
||||
encs += [i]
|
||||
elif i in ends:
|
||||
if len(encs) < 1 or contr[encs[-1]] != i:
|
||||
raise IndexCreatorValueException("Missing opening enclosure for \'%s\'" % i, self.cnt_line_nr(d, st))
|
||||
del encs[-1]
|
||||
|
||||
if len(encs) > 0:
|
||||
raise IndexCreatorValueException("Missing closing enclosure for \'%s\'" % encs[0], self.cnt_line_nr(d, st))
|
||||
|
||||
def check_adjacents(self, d, st):
|
||||
def std_check(d, n):
|
||||
if n == 0:
|
||||
prev = -1
|
||||
else:
|
||||
prev = d[n - 1][1] if d[n - 1][0] == token.OP else d[n - 1][0]
|
||||
|
||||
cur = d[n][1] if d[n][0] == token.OP else d[n][0]
|
||||
|
||||
# there always is an endmarker at the end, but this is a precaution
|
||||
if n + 2 > len(d):
|
||||
nex = -1
|
||||
else:
|
||||
nex = d[n + 1][1] if d[n + 1][0] == token.OP else d[n + 1][0]
|
||||
|
||||
if prev not in self.allowed_adjacent[cur]:
|
||||
raise IndexCreatorValueException("Wrong left value of the %s" % cur, self.cnt_line_nr(line, st))
|
||||
|
||||
# there is an assumption that whole data always ends with 0 marker, the idea prolly needs a rewritting to allow more whitespaces
|
||||
# between tokens, so it will be handled anyway
|
||||
elif nex not in self.allowed_adjacent[cur][prev]:
|
||||
raise IndexCreatorValueException("Wrong right value of the %s" % cur, self.cnt_line_nr(line, st))
|
||||
|
||||
for n, (t, i, _, _, line) in enumerate(d):
|
||||
if t == token.NAME or t == token.STRING:
|
||||
if n + 1 < len(d) and d[n + 1][0] in [token.NAME, token.STRING]:
|
||||
raise IndexCreatorValueException("Did you forget about an operator in between?", self.cnt_line_nr(line, st))
|
||||
elif i in self.allowed_adjacent:
|
||||
std_check(d, n)
|
||||
|
||||
def check_colons(self, d, st):
|
||||
cnt = 0
|
||||
br = 0
|
||||
|
||||
def check_ret_args_nr(a, s):
|
||||
c_b_cnt = 0
|
||||
s_b_cnt = 0
|
||||
n_b_cnt = 0
|
||||
comas_cnt = 0
|
||||
for _, i, _, _, line in a:
|
||||
|
||||
if c_b_cnt == n_b_cnt == s_b_cnt == 0:
|
||||
if i == ',':
|
||||
comas_cnt += 1
|
||||
if (s == 1 and comas_cnt > 1) or (s == 2 and comas_cnt > 0):
|
||||
raise IndexCreatorFunctionException("Too much arguments to return", self.cnt_line_nr(line, st))
|
||||
if s == 0 and comas_cnt > 0:
|
||||
raise IndexCreatorValueException("A coma here doesn't make any sense", self.cnt_line_nr(line, st))
|
||||
|
||||
elif i == ':':
|
||||
if s == 0:
|
||||
raise IndexCreatorValueException("A colon here doesn't make any sense", self.cnt_line_nr(line, st))
|
||||
raise IndexCreatorFunctionException("Two colons don't make any sense", self.cnt_line_nr(line, st))
|
||||
|
||||
if i == '{':
|
||||
c_b_cnt += 1
|
||||
elif i == '}':
|
||||
c_b_cnt -= 1
|
||||
elif i == '(':
|
||||
n_b_cnt += 1
|
||||
elif i == ')':
|
||||
n_b_cnt -= 1
|
||||
elif i == '[':
|
||||
s_b_cnt += 1
|
||||
elif i == ']':
|
||||
s_b_cnt -= 1
|
||||
|
||||
def check_if_empty(a):
|
||||
for i in a:
|
||||
if i not in [token.NEWLINE, token.INDENT, token.ENDMARKER]:
|
||||
return False
|
||||
return True
|
||||
if st == 0:
|
||||
check_ret_args_nr(d, st)
|
||||
return
|
||||
|
||||
for n, i in enumerate(d):
|
||||
if i[1] == ':':
|
||||
if br == 0:
|
||||
if len(d) < n or check_if_empty(d[n + 1:]):
|
||||
raise IndexCreatorValueException(
|
||||
"Empty return value", self.cnt_line_nr(i[4], st))
|
||||
elif len(d) >= n:
|
||||
check_ret_args_nr(d[n + 1:], st)
|
||||
return cnt
|
||||
else:
|
||||
cnt += 1
|
||||
elif i[1] == '{':
|
||||
br += 1
|
||||
elif i[1] == '}':
|
||||
br -= 1
|
||||
check_ret_args_nr(d, st)
|
||||
return -1
|
||||
|
||||
def check_for_2nd_arg(self, d):
|
||||
c_b_cnt = 0 # curly brackets counter '{}'
|
||||
s_b_cnt = 0 # square brackets counter '[]'
|
||||
n_b_cnt = 0 # normal brackets counter '()'
|
||||
|
||||
def check_2nd_arg(d, ind):
|
||||
d = d[ind[0]:]
|
||||
for t, i, (n, r), _, line in d:
|
||||
if i == '{' or i is None:
|
||||
return 0
|
||||
elif t == token.NAME:
|
||||
self.known_dicts_in_mkv.append((i, (n, r)))
|
||||
return 0
|
||||
elif t == token.STRING or t == token.NUMBER:
|
||||
raise IndexCreatorValueException("Second return value of make_key_value function has to be a dictionary!", self.cnt_line_nr(line, 1))
|
||||
|
||||
for ind in enumerate(d):
|
||||
t, i, _, _, _ = ind[1]
|
||||
if s_b_cnt == n_b_cnt == c_b_cnt == 0:
|
||||
if i == ',':
|
||||
return check_2nd_arg(d, ind)
|
||||
elif (t == token.NAME and i not in self.funcs) or i == '{':
|
||||
self.is_one_arg_enough = True
|
||||
|
||||
if i == '{':
|
||||
c_b_cnt += 1
|
||||
self.is_one_arg_enough = True
|
||||
elif i == '}':
|
||||
c_b_cnt -= 1
|
||||
elif i == '(':
|
||||
n_b_cnt += 1
|
||||
elif i == ')':
|
||||
n_b_cnt -= 1
|
||||
elif i == '[':
|
||||
s_b_cnt += 1
|
||||
elif i == ']':
|
||||
s_b_cnt -= 1
|
||||
return -1
|
||||
|
||||
def cnt_line_nr(self, l, stage):
|
||||
nr = -1
|
||||
for n, i in enumerate(self.predata[stage]):
|
||||
# print i,"|||",i.strip(),"|||",l
|
||||
if l == i.strip():
|
||||
nr = n
|
||||
if nr == -1:
|
||||
return -1
|
||||
|
||||
if stage == 0:
|
||||
return nr + 1
|
||||
elif stage == 1:
|
||||
return nr + self.cnt_lines[0] + (self.cnt_lines[2] - 1 if self.funcs_rev else 0)
|
||||
elif stage == 2:
|
||||
return nr + self.cnt_lines[0] + (self.cnt_lines[1] - 1 if not self.funcs_rev else 0)
|
||||
|
||||
return -1
|
||||
|
||||
def handle_prop_line(self, d):
|
||||
d_len = len(d)
|
||||
if d[d_len - 1][0] == token.ENDMARKER:
|
||||
d_len -= 1
|
||||
|
||||
if d_len < 3:
|
||||
raise IndexCreatorValueException("Can't handle properity assingment ", self.cnt_line_nr(d[0][4], 0))
|
||||
|
||||
if not d[1][1] in self.props_assign:
|
||||
raise IndexCreatorValueException(
|
||||
"Did you forget : or =?", self.cnt_line_nr(d[0][4], 0))
|
||||
|
||||
if d[0][0] == token.NAME or d[0][0] == token.STRING:
|
||||
if d[0][1] in self.props_set:
|
||||
raise IndexCreatorValueException("Properity %s is set more than once" % d[0][1], self.cnt_line_nr(d[0][4], 0))
|
||||
self.props_set += [d[0][1]]
|
||||
if d[0][1] == "type" or d[0][1] == "name":
|
||||
t, tk, _, _, line = d[2]
|
||||
|
||||
if d_len > 3:
|
||||
raise IndexCreatorValueException(
|
||||
"Wrong value to assign", self.cnt_line_nr(line, 0))
|
||||
|
||||
if t == token.STRING:
|
||||
m = re.search('\s*(?P<a>[\'\"]+)(.*?)(?P=a)\s*', tk)
|
||||
if m:
|
||||
tk = m.groups()[1]
|
||||
elif t != token.NAME:
|
||||
raise IndexCreatorValueException(
|
||||
"Wrong value to assign", self.cnt_line_nr(line, 0))
|
||||
|
||||
if d[0][1] == "type":
|
||||
if d[2][1] == "TreeBasedIndex":
|
||||
self.custom_header.add("from CodernityDB3.tree_index import TreeBasedIndex\n")
|
||||
elif d[2][1] == "MultiTreeBasedIndex":
|
||||
self.custom_header.add("from CodernityDB3.tree_index import MultiTreeBasedIndex\n")
|
||||
elif d[2][1] == "MultiHashIndex":
|
||||
self.custom_header.add("from CodernityDB3.hash_index import MultiHashIndex\n")
|
||||
self.tokens_head.insert(2, tk)
|
||||
self.index_type = tk
|
||||
else:
|
||||
self.index_name = tk
|
||||
return
|
||||
else:
|
||||
self.tokens += ['\n kwargs["' + d[0][1] + '"]']
|
||||
else:
|
||||
raise IndexCreatorValueException("Can't handle properity assingment ", self.cnt_line_nr(d[0][4], 0))
|
||||
|
||||
self.tokens += ['=']
|
||||
|
||||
self.check_adjacents(d[2:], 0)
|
||||
self.check_colons(d[2:], 0)
|
||||
|
||||
for i in d[2:]:
|
||||
self.tokens += [i[1]]
|
||||
|
||||
def generate_func(self, t, tk, pos_start, pos_end, line, hdata, stage):
|
||||
if self.last_line[stage] != -1 and pos_start[0] > self.last_line[stage] and line != '':
|
||||
raise IndexCreatorFunctionException("This line will never be executed!", self.cnt_line_nr(line, stage))
|
||||
if t == 0:
|
||||
return
|
||||
|
||||
if pos_start[1] == 0:
|
||||
if self.line_cons[stage][pos_start[0] - 1] == -1:
|
||||
self.tokens += ['\n return']
|
||||
self.last_line[stage] = pos_start[0]
|
||||
else:
|
||||
self.tokens += ['\n if']
|
||||
elif tk == ':' and self.line_cons[stage][pos_start[0] - 1] > -1:
|
||||
if self.line_cons[stage][pos_start[0] - 1] == 0:
|
||||
self.tokens += [':\n return']
|
||||
return
|
||||
self.line_cons[stage][pos_start[0] - 1] -= 1
|
||||
|
||||
if tk in self.logic2:
|
||||
# print tk
|
||||
if line[pos_start[1] - 1] != tk and line[pos_start[1] + 1] != tk:
|
||||
self.tokens += [tk]
|
||||
if line[pos_start[1] - 1] != tk and line[pos_start[1] + 1] == tk:
|
||||
if tk == '&':
|
||||
self.tokens += ['and']
|
||||
else:
|
||||
self.tokens += ['or']
|
||||
return
|
||||
|
||||
if self.brackets != 0:
|
||||
def search_through_known_dicts(a):
|
||||
for i, (n, r) in self.known_dicts_in_mkv:
|
||||
if i == tk and r > pos_start[1] and n == pos_start[0] and hdata == 'data':
|
||||
return True
|
||||
return False
|
||||
|
||||
if t == token.NAME and len(self.funcs_stack) > 0 and self.funcs_stack[-1][0] == 'md5' and search_through_known_dicts(tk):
|
||||
raise IndexCreatorValueException("Second value returned by make_key_value for sure isn't a dictionary ", self.cnt_line_nr(line, 1))
|
||||
|
||||
if tk == ')':
|
||||
self.cur_brackets -= 1
|
||||
if len(self.funcs_stack) > 0 and self.cur_brackets == self.funcs_stack[-1][1]:
|
||||
self.tokens += [tk]
|
||||
self.tokens += self.funcs[self.funcs_stack[-1][0]][1]
|
||||
del self.funcs_stack[-1]
|
||||
return
|
||||
if tk == '(':
|
||||
self.cur_brackets += 1
|
||||
|
||||
if tk in self.none:
|
||||
self.tokens += ['None']
|
||||
return
|
||||
|
||||
if t == token.NAME and tk not in self.logic and tk != hdata:
|
||||
if tk not in self.funcs:
|
||||
self.tokens += [hdata + '["' + tk + '"]']
|
||||
else:
|
||||
self.tokens += self.funcs[tk][0]
|
||||
if tk in self.funcs_with_body:
|
||||
self.funcs_with_body[tk] = (
|
||||
self.funcs_with_body[tk][0], True)
|
||||
self.custom_header.add(self.handle_int_imports.get(tk))
|
||||
self.funcs_stack += [(tk, self.cur_brackets)]
|
||||
else:
|
||||
self.tokens += [tk]
|
||||
|
||||
def handle_make_value(self, t, tk, pos_start, pos_end, line):
|
||||
self.generate_func(t, tk, pos_start, pos_end, line, 'data', 1)
|
||||
|
||||
def handle_make_key(self, t, tk, pos_start, pos_end, line):
|
||||
self.generate_func(t, tk, pos_start, pos_end, line, 'key', 2)
|
||||
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import functools
|
||||
from heapq import nsmallest
|
||||
from operator import itemgetter
|
||||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
from collections import Counter
|
||||
except ImportError:
|
||||
class Counter(dict):
|
||||
'Mapping where default values are zero'
|
||||
def __missing__(self, key):
|
||||
return 0
|
||||
|
||||
|
||||
def cache1lvl(maxsize=100):
|
||||
"""
|
||||
modified version of http://code.activestate.com/recipes/498245/
|
||||
"""
|
||||
def decorating_function(user_function):
|
||||
cache = {}
|
||||
use_count = Counter()
|
||||
|
||||
@functools.wraps(user_function)
|
||||
def wrapper(key, *args, **kwargs):
|
||||
try:
|
||||
result = cache[key]
|
||||
except KeyError:
|
||||
if len(cache) == maxsize:
|
||||
for k, _ in nsmallest(maxsize // 10 or 1,
|
||||
iter(use_count.items()),
|
||||
key=itemgetter(1)):
|
||||
del cache[k], use_count[k]
|
||||
cache[key] = user_function(key, *args, **kwargs)
|
||||
result = cache[key]
|
||||
# result = user_function(obj, key, *args, **kwargs)
|
||||
finally:
|
||||
use_count[key] += 1
|
||||
return result
|
||||
|
||||
def clear():
|
||||
cache.clear()
|
||||
use_count.clear()
|
||||
|
||||
def delete(key):
|
||||
try:
|
||||
del cache[key]
|
||||
del use_count[key]
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
wrapper.clear = clear
|
||||
wrapper.cache = cache
|
||||
wrapper.delete = delete
|
||||
return wrapper
|
||||
return decorating_function
|
||||
|
||||
|
||||
def twolvl_iterator(dict):
|
||||
for k, v in dict.items():
|
||||
for kk, vv in v.items():
|
||||
yield k, kk, vv
|
||||
|
||||
|
||||
def cache2lvl(maxsize=100):
|
||||
"""
|
||||
modified version of http://code.activestate.com/recipes/498245/
|
||||
"""
|
||||
def decorating_function(user_function):
|
||||
cache = {}
|
||||
use_count = defaultdict(Counter)
|
||||
|
||||
@functools.wraps(user_function)
|
||||
def wrapper(*args, **kwargs):
|
||||
# return user_function(*args, **kwargs)
|
||||
try:
|
||||
result = cache[args[0]][args[1]]
|
||||
except KeyError:
|
||||
if wrapper.cache_size == maxsize:
|
||||
to_delete = maxsize // 10 or 1
|
||||
for k1, k2, v in nsmallest(to_delete,
|
||||
twolvl_iterator(use_count),
|
||||
key=itemgetter(2)):
|
||||
del cache[k1][k2], use_count[k1][k2]
|
||||
if not cache[k1]:
|
||||
del cache[k1]
|
||||
del use_count[k1]
|
||||
wrapper.cache_size -= to_delete
|
||||
result = user_function(*args, **kwargs)
|
||||
try:
|
||||
cache[args[0]][args[1]] = result
|
||||
except KeyError:
|
||||
cache[args[0]] = {args[1]: result}
|
||||
wrapper.cache_size += 1
|
||||
finally:
|
||||
use_count[args[0]][args[1]] += 1
|
||||
return result
|
||||
|
||||
def clear():
|
||||
cache.clear()
|
||||
use_count.clear()
|
||||
|
||||
def delete(key, inner_key=None):
|
||||
if inner_key is not None:
|
||||
try:
|
||||
del cache[key][inner_key]
|
||||
del use_count[key][inner_key]
|
||||
if not cache[key]:
|
||||
del cache[key]
|
||||
del use_count[key]
|
||||
wrapper.cache_size -= 1
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
try:
|
||||
wrapper.cache_size -= len(cache[key])
|
||||
del cache[key]
|
||||
del use_count[key]
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
wrapper.clear = clear
|
||||
wrapper.cache = cache
|
||||
wrapper.delete = delete
|
||||
wrapper.cache_size = 0
|
||||
return wrapper
|
||||
return decorating_function
|
||||
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import functools
|
||||
from heapq import nsmallest
|
||||
from operator import itemgetter
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
try:
|
||||
from collections import Counter
|
||||
except ImportError:
|
||||
class Counter(dict):
|
||||
'Mapping where default values are zero'
|
||||
def __missing__(self, key):
|
||||
return 0
|
||||
|
||||
|
||||
def twolvl_iterator(dict):
|
||||
for k, v in dict.items():
|
||||
for kk, vv in v.items():
|
||||
yield k, kk, vv
|
||||
|
||||
|
||||
def create_cache1lvl(lock_obj):
|
||||
def cache1lvl(maxsize=100):
|
||||
"""
|
||||
modified version of http://code.activestate.com/recipes/498245/
|
||||
"""
|
||||
def decorating_function(user_function):
|
||||
cache = {}
|
||||
use_count = Counter()
|
||||
lock = lock_obj()
|
||||
|
||||
@functools.wraps(user_function)
|
||||
def wrapper(key, *args, **kwargs):
|
||||
try:
|
||||
result = cache[key]
|
||||
except KeyError:
|
||||
with lock:
|
||||
if len(cache) == maxsize:
|
||||
for k, _ in nsmallest(maxsize // 10 or 1,
|
||||
iter(use_count.items()),
|
||||
key=itemgetter(1)):
|
||||
del cache[k], use_count[k]
|
||||
cache[key] = user_function(key, *args, **kwargs)
|
||||
result = cache[key]
|
||||
use_count[key] += 1
|
||||
else:
|
||||
with lock:
|
||||
use_count[key] += 1
|
||||
return result
|
||||
|
||||
def clear():
|
||||
cache.clear()
|
||||
use_count.clear()
|
||||
|
||||
def delete(key):
|
||||
try:
|
||||
del cache[key]
|
||||
del use_count[key]
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
wrapper.clear = clear
|
||||
wrapper.cache = cache
|
||||
wrapper.delete = delete
|
||||
return wrapper
|
||||
return decorating_function
|
||||
return cache1lvl
|
||||
|
||||
|
||||
def create_cache2lvl(lock_obj):
|
||||
def cache2lvl(maxsize=100):
|
||||
"""
|
||||
modified version of http://code.activestate.com/recipes/498245/
|
||||
"""
|
||||
def decorating_function(user_function):
|
||||
cache = {}
|
||||
use_count = defaultdict(Counter)
|
||||
lock = lock_obj()
|
||||
|
||||
@functools.wraps(user_function)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
result = cache[args[0]][args[1]]
|
||||
except KeyError:
|
||||
with lock:
|
||||
if wrapper.cache_size == maxsize:
|
||||
to_delete = maxsize / 10 or 1
|
||||
for k1, k2, v in nsmallest(to_delete,
|
||||
twolvl_iterator(
|
||||
use_count),
|
||||
key=itemgetter(2)):
|
||||
del cache[k1][k2], use_count[k1][k2]
|
||||
if not cache[k1]:
|
||||
del cache[k1]
|
||||
del use_count[k1]
|
||||
wrapper.cache_size -= to_delete
|
||||
result = user_function(*args, **kwargs)
|
||||
try:
|
||||
cache[args[0]][args[1]] = result
|
||||
except KeyError:
|
||||
cache[args[0]] = {args[1]: result}
|
||||
use_count[args[0]][args[1]] += 1
|
||||
wrapper.cache_size += 1
|
||||
else:
|
||||
use_count[args[0]][args[1]] += 1
|
||||
return result
|
||||
|
||||
def clear():
|
||||
cache.clear()
|
||||
use_count.clear()
|
||||
|
||||
def delete(key, *args):
|
||||
if args:
|
||||
try:
|
||||
del cache[key][args[0]]
|
||||
del use_count[key][args[0]]
|
||||
if not cache[key]:
|
||||
del cache[key]
|
||||
del use_count[key]
|
||||
wrapper.cache_size -= 1
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
wrapper.cache_size -= len(cache[key])
|
||||
del cache[key]
|
||||
del use_count[key]
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
wrapper.clear = clear
|
||||
wrapper.cache = cache
|
||||
wrapper.delete = delete
|
||||
wrapper.cache_size = 0
|
||||
return wrapper
|
||||
return decorating_function
|
||||
return cache2lvl
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from CodernityDB3.database import Database
|
||||
import shutil
|
||||
import os
|
||||
|
||||
|
||||
def migrate(source, destination):
|
||||
"""
|
||||
Very basic for now
|
||||
"""
|
||||
dbs = Database(source)
|
||||
dbt = Database(destination)
|
||||
dbs.open()
|
||||
dbt.create()
|
||||
dbt.close()
|
||||
for curr in os.listdir(os.path.join(dbs.path, '_indexes')):
|
||||
if curr != '00id.py':
|
||||
shutil.copyfile(os.path.join(dbs.path, '_indexes', curr),
|
||||
os.path.join(dbt.path, '_indexes', curr))
|
||||
dbt.open()
|
||||
for c in dbs.all('id'):
|
||||
del c['_rev']
|
||||
dbt.insert(c)
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
migrate(sys.argv[1], sys.argv[2])
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from random import getrandbits, randrange
|
||||
import uuid
|
||||
|
||||
|
||||
class NONE:
|
||||
"""
|
||||
It's inteded to be None but different,
|
||||
for internal use only!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def random_hex_32():
|
||||
return uuid.UUID(int=getrandbits(128), version=4).hex
|
||||
|
||||
|
||||
def random_hex_4(*args, **kwargs):
|
||||
return '%04x' % randrange(256 ** 2)
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from CodernityDB3.misc import NONE
|
||||
|
||||
|
||||
def __patch(obj, name, new):
|
||||
n = NONE()
|
||||
orig = getattr(obj, name, n)
|
||||
if orig is not n:
|
||||
if orig == new:
|
||||
raise Exception("Shouldn't happen, new and orig are the same")
|
||||
setattr(obj, name, new)
|
||||
return
|
||||
|
||||
|
||||
def patch_cache_lfu(lock_obj):
|
||||
"""
|
||||
Patnches cache mechanizm to be thread safe (gevent ones also)
|
||||
|
||||
.. note::
|
||||
|
||||
It's internal CodernityDB mechanizm, it will be called when needed
|
||||
|
||||
"""
|
||||
from . import lfu_cache
|
||||
from . import lfu_cache_with_lock
|
||||
lfu_lock1lvl = lfu_cache_with_lock.create_cache1lvl(lock_obj)
|
||||
lfu_lock2lvl = lfu_cache_with_lock.create_cache2lvl(lock_obj)
|
||||
__patch(lfu_cache, 'cache1lvl', lfu_lock1lvl)
|
||||
__patch(lfu_cache, 'cache2lvl', lfu_lock2lvl)
|
||||
|
||||
|
||||
def patch_cache_rr(lock_obj):
|
||||
"""
|
||||
Patches cache mechanizm to be thread safe (gevent ones also)
|
||||
|
||||
.. note::
|
||||
|
||||
It's internal CodernityDB mechanizm, it will be called when needed
|
||||
|
||||
"""
|
||||
from . import rr_cache
|
||||
from . import rr_cache_with_lock
|
||||
rr_lock1lvl = rr_cache_with_lock.create_cache1lvl(lock_obj)
|
||||
rr_lock2lvl = rr_cache_with_lock.create_cache2lvl(lock_obj)
|
||||
__patch(rr_cache, 'cache1lvl', rr_lock1lvl)
|
||||
__patch(rr_cache, 'cache2lvl', rr_lock2lvl)
|
||||
|
||||
|
||||
def patch_flush_fsync(db_obj):
|
||||
"""
|
||||
Will always execute index.fsync after index.flush.
|
||||
|
||||
.. note::
|
||||
|
||||
It's for advanced users, use when you understand difference between `flush` and `fsync`, and when you definitely need that.
|
||||
|
||||
It's important to call it **AFTER** database has all indexes etc (after db.create or db.open)
|
||||
|
||||
Example usage::
|
||||
|
||||
...
|
||||
db = Database('/tmp/patch_demo')
|
||||
db.create()
|
||||
patch_flush_fsync(db)
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
def always_fsync(ind_obj):
|
||||
def _inner():
|
||||
ind_obj.orig_flush()
|
||||
ind_obj.fsync()
|
||||
return _inner
|
||||
|
||||
for index in db_obj.indexes:
|
||||
setattr(index, 'orig_flush', index.flush)
|
||||
setattr(index, 'flush', always_fsync(index))
|
||||
|
||||
setattr(db_obj, 'orig_flush', db_obj.flush)
|
||||
setattr(db_obj, 'flush', always_fsync(db_obj))
|
||||
|
||||
return
|
||||
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import functools
|
||||
from random import choice
|
||||
|
||||
|
||||
def cache1lvl(maxsize=100):
|
||||
def decorating_function(user_function):
|
||||
cache1lvl = {}
|
||||
|
||||
@functools.wraps(user_function)
|
||||
def wrapper(key, *args, **kwargs):
|
||||
if isinstance(key, bytes):
|
||||
key = key.decode()
|
||||
# print("cachedddd", key) ## TODO
|
||||
try:
|
||||
#result = cache1lvl[key]
|
||||
result = cache1lvl[key]
|
||||
except KeyError:
|
||||
if len(cache1lvl) == maxsize:
|
||||
for i in range(maxsize // 10 or 1):
|
||||
del cache1lvl[choice(list(cache1lvl.keys()))]
|
||||
## print("#" * 10, key) # TODO
|
||||
## print(user_function) # TODO
|
||||
## print("cache1lvl", key, user_function) # TODO
|
||||
## print(cache1lvl) # TODO
|
||||
cache1lvl[key] = user_function(key, *args, **kwargs)
|
||||
## print(cache1lvl) # TODO
|
||||
result = cache1lvl[key]
|
||||
## print("result caching", result) # TODO
|
||||
# result = user_function(obj, key, *args, **kwargs)
|
||||
if isinstance(result, bytes):
|
||||
result = key.decode()
|
||||
## print("r" * 20, result) # TODO
|
||||
return result
|
||||
|
||||
def clear():
|
||||
cache1lvl.clear()
|
||||
|
||||
def delete(key):
|
||||
if isinstance(key, bytes):
|
||||
key = key.decode()
|
||||
try:
|
||||
del cache1lvl[key]
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
wrapper.clear = clear
|
||||
wrapper.cache = cache1lvl
|
||||
wrapper.delete = delete
|
||||
return wrapper
|
||||
return decorating_function
|
||||
|
||||
|
||||
def cache2lvl(maxsize=100):
|
||||
def decorating_function(user_function):
|
||||
cache = {}
|
||||
|
||||
@functools.wraps(user_function)
|
||||
def wrapper(*args, **kwargs):
|
||||
# return user_function(*args, **kwargs)
|
||||
try:
|
||||
result = cache[args[0]][args[1]]
|
||||
except KeyError:
|
||||
# print wrapper.cache_size
|
||||
if wrapper.cache_size == maxsize:
|
||||
to_delete = maxsize // 10 or 1
|
||||
for i in range(to_delete):
|
||||
key1 = choice(list(cache.keys()))
|
||||
key2 = choice(list(cache[key1].keys()))
|
||||
del cache[key1][key2]
|
||||
if not cache[key1]:
|
||||
del cache[key1]
|
||||
wrapper.cache_size -= to_delete
|
||||
# print wrapper.cache_size
|
||||
result = user_function(*args, **kwargs)
|
||||
try:
|
||||
cache[args[0]][args[1]] = result
|
||||
except KeyError:
|
||||
cache[args[0]] = {args[1]: result}
|
||||
wrapper.cache_size += 1
|
||||
return result
|
||||
|
||||
def clear():
|
||||
cache.clear()
|
||||
wrapper.cache_size = 0
|
||||
|
||||
def delete(key, inner_key=None):
|
||||
if inner_key:
|
||||
try:
|
||||
del cache[key][inner_key]
|
||||
if not cache[key]:
|
||||
del cache[key]
|
||||
wrapper.cache_size -= 1
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
wrapper.cache_size -= len(cache[key])
|
||||
del cache[key]
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
wrapper.clear = clear
|
||||
wrapper.cache = cache
|
||||
wrapper.delete = delete
|
||||
wrapper.cache_size = 0
|
||||
return wrapper
|
||||
return decorating_function
|
||||
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import functools
|
||||
from random import choice
|
||||
|
||||
|
||||
def create_cache1lvl(lock_obj):
|
||||
def cache1lvl(maxsize=100):
|
||||
def decorating_function(user_function):
|
||||
cache = {}
|
||||
lock = lock_obj()
|
||||
|
||||
@functools.wraps(user_function)
|
||||
def wrapper(key, *args, **kwargs):
|
||||
try:
|
||||
result = cache[key]
|
||||
except KeyError:
|
||||
with lock:
|
||||
if len(cache) == maxsize:
|
||||
for i in range(maxsize // 10 or 1):
|
||||
del cache[choice(list(cache.keys()))]
|
||||
cache[key] = user_function(key, *args, **kwargs)
|
||||
result = cache[key]
|
||||
return result
|
||||
|
||||
def clear():
|
||||
cache.clear()
|
||||
|
||||
def delete(key):
|
||||
try:
|
||||
del cache[key]
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
wrapper.clear = clear
|
||||
wrapper.cache = cache
|
||||
wrapper.delete = delete
|
||||
return wrapper
|
||||
return decorating_function
|
||||
return cache1lvl
|
||||
|
||||
|
||||
def create_cache2lvl(lock_obj):
|
||||
def cache2lvl(maxsize=100):
|
||||
def decorating_function(user_function):
|
||||
cache = {}
|
||||
lock = lock_obj()
|
||||
|
||||
@functools.wraps(user_function)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
result = cache[args[0]][args[1]]
|
||||
except KeyError:
|
||||
with lock:
|
||||
if wrapper.cache_size == maxsize:
|
||||
to_delete = maxsize // 10 or 1
|
||||
for i in range(to_delete):
|
||||
key1 = choice(list(cache.keys()))
|
||||
key2 = choice(list(cache[key1].keys()))
|
||||
del cache[key1][key2]
|
||||
if not cache[key1]:
|
||||
del cache[key1]
|
||||
wrapper.cache_size -= to_delete
|
||||
result = user_function(*args, **kwargs)
|
||||
try:
|
||||
cache[args[0]][args[1]] = result
|
||||
except KeyError:
|
||||
cache[args[0]] = {args[1]: result}
|
||||
wrapper.cache_size += 1
|
||||
return result
|
||||
|
||||
def clear():
|
||||
cache.clear()
|
||||
wrapper.cache_size = 0
|
||||
|
||||
def delete(key, *args):
|
||||
if args:
|
||||
try:
|
||||
del cache[key][args[0]]
|
||||
if not cache[key]:
|
||||
del cache[key]
|
||||
wrapper.cache_size -= 1
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
wrapper.cache_size -= len(cache[key])
|
||||
del cache[key]
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
wrapper.clear = clear
|
||||
wrapper.cache = cache
|
||||
wrapper.delete = delete
|
||||
wrapper.cache_size = 0
|
||||
return wrapper
|
||||
return decorating_function
|
||||
return cache2lvl
|
||||
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from CodernityDB3.hash_index import UniqueHashIndex, HashIndex
|
||||
from CodernityDB3.sharded_index import ShardedIndex
|
||||
from CodernityDB3.index import IndexPreconditionsException
|
||||
|
||||
from random import getrandbits
|
||||
import uuid
|
||||
|
||||
|
||||
class IU_ShardedUniqueHashIndex(ShardedIndex):
|
||||
|
||||
custom_header = """import uuid
|
||||
from random import getrandbits
|
||||
from CodernityDB3.sharded_index import ShardedIndex
|
||||
"""
|
||||
|
||||
def __init__(self, db_path, name, *args, **kwargs):
|
||||
if kwargs.get('sh_nums', 0) > 255:
|
||||
raise IndexPreconditionsException("Too many shards")
|
||||
kwargs['ind_class'] = UniqueHashIndex
|
||||
super(IU_ShardedUniqueHashIndex, self).__init__(db_path,
|
||||
name, *args, **kwargs)
|
||||
self.patchers.append(self.wrap_insert_id_index)
|
||||
|
||||
@staticmethod
|
||||
def wrap_insert_id_index(db_obj, clean=False):
|
||||
def _insert_id_index(_rev, data):
|
||||
"""
|
||||
Performs insert on **id** index.
|
||||
"""
|
||||
_id, value = db_obj.id_ind.make_key_value(data) # may be improved
|
||||
trg_shard = _id[:2]
|
||||
storage = db_obj.id_ind.shards_r[trg_shard].storage
|
||||
start, size = storage.insert(value)
|
||||
db_obj.id_ind.insert(_id, _rev, start, size)
|
||||
return _id
|
||||
if not clean:
|
||||
if hasattr(db_obj, '_insert_id_index_orig'):
|
||||
raise IndexPreconditionsException(
|
||||
"Already patched, something went wrong")
|
||||
setattr(db_obj, "_insert_id_index_orig", db_obj._insert_id_index)
|
||||
setattr(db_obj, "_insert_id_index", _insert_id_index)
|
||||
else:
|
||||
setattr(db_obj, "_insert_id_index", db_obj._insert_id_index_orig)
|
||||
delattr(db_obj, "_insert_id_index_orig")
|
||||
|
||||
def create_key(self):
|
||||
h = uuid.UUID(int=getrandbits(128), version=4).hex
|
||||
trg = self.last_used + 1
|
||||
if trg >= self.sh_nums:
|
||||
trg = 0
|
||||
self.last_used = trg
|
||||
h = '%02x%30s' % (trg, h[2:])
|
||||
return h
|
||||
|
||||
def delete(self, key, *args, **kwargs):
|
||||
trg_shard = key[:2]
|
||||
op = self.shards_r[trg_shard]
|
||||
return op.delete(key, *args, **kwargs)
|
||||
|
||||
def update(self, key, *args, **kwargs):
|
||||
trg_shard = key[:2]
|
||||
self.last_used = int(trg_shard, 16)
|
||||
op = self.shards_r[trg_shard]
|
||||
return op.update(key, *args, **kwargs)
|
||||
|
||||
def insert(self, key, *args, **kwargs):
|
||||
trg_shard = key[:2] # in most cases it's in create_key BUT not always
|
||||
self.last_used = int(key[:2], 16)
|
||||
op = self.shards_r[trg_shard]
|
||||
return op.insert(key, *args, **kwargs)
|
||||
|
||||
def get(self, key, *args, **kwargs):
|
||||
trg_shard = key[:2]
|
||||
self.last_used = int(trg_shard, 16)
|
||||
op = self.shards_r[trg_shard]
|
||||
return op.get(key, *args, **kwargs)
|
||||
|
||||
|
||||
class ShardedUniqueHashIndex(IU_ShardedUniqueHashIndex):
|
||||
|
||||
# allow unique hash to be used directly
|
||||
custom_header = 'from CodernityDB3.sharded_hash import IU_ShardedUniqueHashIndex'
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class IU_ShardedHashIndex(ShardedIndex):
|
||||
|
||||
custom_header = """from CodernityDB3.sharded_index import ShardedIndex"""
|
||||
|
||||
def __init__(self, db_path, name, *args, **kwargs):
|
||||
kwargs['ind_class'] = HashIndex
|
||||
super(IU_ShardedHashIndex, self).__init__(db_path, name, *
|
||||
args, **kwargs)
|
||||
|
||||
def calculate_shard(self, key):
|
||||
"""
|
||||
Must be implemented. It has to return shard to be used by key
|
||||
|
||||
:param key: key
|
||||
:returns: target shard
|
||||
:rtype: int
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete(self, doc_id, key, *args, **kwargs):
|
||||
trg_shard = self.calculate_shard(key)
|
||||
op = self.shards_r[trg_shard]
|
||||
return op.delete(doc_id, key, *args, **kwargs)
|
||||
|
||||
def insert(self, doc_id, key, *args, **kwargs):
|
||||
trg_shard = self.calculate_shard(key)
|
||||
op = self.shards_r[trg_shard]
|
||||
return op.insert(doc_id, key, *args, **kwargs)
|
||||
|
||||
def update(self, doc_id, key, *args, **kwargs):
|
||||
trg_shard = self.calculate_shard(key)
|
||||
op = self.shards_r[trg_shard]
|
||||
return op.insert(doc_id, key, *args, **kwargs)
|
||||
|
||||
def get(self, key, *args, **kwargs):
|
||||
trg_shard = self.calculate_shard(key)
|
||||
op = self.shards_r[trg_shard]
|
||||
return op.get(key, *args, **kwargs)
|
||||
|
||||
|
||||
class ShardedHashIndex(IU_ShardedHashIndex):
|
||||
pass
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from CodernityDB3.index import Index
|
||||
# from CodernityDB3.env import cdb_environment
|
||||
# import warnings
|
||||
|
||||
|
||||
class ShardedIndex(Index):
|
||||
|
||||
def __init__(self, db_path, name, *args, **kwargs):
|
||||
"""
|
||||
There are 3 additional parameters. You have to hardcode them in your custom class. **NEVER** use directly
|
||||
|
||||
:param int sh_nums: how many shards should be
|
||||
:param class ind_class: Index class to use (HashIndex or your custom one)
|
||||
:param bool use_make_keys: if True, `make_key`, and `make_key_value` will be overriden with those from first shard
|
||||
|
||||
The rest parameters are passed straight to `ind_class` shards.
|
||||
|
||||
"""
|
||||
super(ShardedIndex, self).__init__(db_path, name)
|
||||
try:
|
||||
self.sh_nums = kwargs.pop('sh_nums')
|
||||
except KeyError:
|
||||
self.sh_nums = 5
|
||||
try:
|
||||
ind_class = kwargs.pop('ind_class')
|
||||
except KeyError:
|
||||
raise Exception("ind_class must be given")
|
||||
else:
|
||||
# if not isinstance(ind_class, basestring):
|
||||
# ind_class = ind_class.__name__
|
||||
self.ind_class = ind_class
|
||||
if 'use_make_keys' in kwargs:
|
||||
self.use_make_keys = kwargs.pop('use_make_keys')
|
||||
else:
|
||||
self.use_make_keys = False
|
||||
self._set_shard_datas(*args, **kwargs)
|
||||
self.patchers = [] # database object patchers
|
||||
|
||||
def _set_shard_datas(self, *args, **kwargs):
|
||||
self.shards = {}
|
||||
self.shards_r = {}
|
||||
# ind_class = globals()[self.ind_class]
|
||||
ind_class = self.ind_class
|
||||
i = 0
|
||||
for sh_name in [self.name + str(x) for x in range(self.sh_nums)]:
|
||||
# dict is better than list in that case
|
||||
self.shards[i] = ind_class(self.db_path, sh_name, *args, **kwargs)
|
||||
self.shards_r['%02x' % i] = self.shards[i]
|
||||
self.shards_r[i] = self.shards[i]
|
||||
i += 1
|
||||
|
||||
if not self.use_make_keys:
|
||||
self.make_key = self.shards[0].make_key
|
||||
self.make_key_value = self.shards[0].make_key_value
|
||||
|
||||
self.last_used = 0
|
||||
|
||||
@property
|
||||
def storage(self):
|
||||
st = self.shards[self.last_used].storage
|
||||
return st
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.shards[self.last_used], name)
|
||||
|
||||
def open_index(self):
|
||||
for curr in self.shards.values():
|
||||
curr.open_index()
|
||||
|
||||
def create_index(self):
|
||||
for curr in self.shards.values():
|
||||
curr.create_index()
|
||||
|
||||
def destroy(self):
|
||||
for curr in self.shards.values():
|
||||
curr.destroy()
|
||||
|
||||
def compact(self):
|
||||
for curr in self.shards.values():
|
||||
curr.compact()
|
||||
|
||||
def reindex(self):
|
||||
for curr in self.shards.values():
|
||||
curr.reindex()
|
||||
|
||||
def all(self, *args, **kwargs):
|
||||
for curr in self.shards.values():
|
||||
for now in curr.all(*args, **kwargs):
|
||||
yield now
|
||||
|
||||
def get_many(self, *args, **kwargs):
|
||||
for curr in self.shards.values():
|
||||
for now in curr.get_many(*args, **kwargs):
|
||||
yield now
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011-2013 Codernity (http://codernity.com)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import struct
|
||||
import shutil
|
||||
import marshal
|
||||
import io
|
||||
|
||||
|
||||
try:
|
||||
from CodernityDB3 import __version__
|
||||
except ImportError:
|
||||
from .__init__ import __version__
|
||||
|
||||
|
||||
class StorageException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DummyStorage(object):
|
||||
"""
|
||||
Storage mostly used to fake real storage
|
||||
"""
|
||||
|
||||
def create(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def close(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def data_from(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def data_to(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
return 0, 0
|
||||
|
||||
def insert(self, *args, **kwargs):
|
||||
return self.save(*args, **kwargs)
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
return 0, 0
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
# def compact(self, *args, **kwargs):
|
||||
# pass
|
||||
|
||||
def fsync(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def flush(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class IU_Storage(object):
|
||||
|
||||
__version__ = __version__
|
||||
|
||||
def __init__(self, db_path, name='main'):
|
||||
if isinstance(db_path, bytes):
|
||||
db_path = db_path.decode()
|
||||
if isinstance(name, bytes):
|
||||
name = name.decode()
|
||||
|
||||
self.db_path = db_path
|
||||
self.name = name
|
||||
self._header_size = 100
|
||||
|
||||
def create(self):
|
||||
if os.path.exists(os.path.join(self.db_path, self.name + "_stor")):
|
||||
raise IOError("Storage already exists!")
|
||||
with io.open(os.path.join(self.db_path, self.name + "_stor"), 'wb') as f:
|
||||
if isinstance(self.__version__, str):
|
||||
new_version = self.__version__.encode()
|
||||
else:
|
||||
new_version = self.__version__
|
||||
f.write(struct.pack(b'10s90s', new_version, b'|||||'))
|
||||
f.close()
|
||||
self._f = io.open(os.path.join(
|
||||
self.db_path, self.name + "_stor"), 'r+b', buffering=0)
|
||||
self.flush()
|
||||
self._f.seek(0, 2)
|
||||
|
||||
def open(self):
|
||||
if not os.path.exists(os.path.join(self.db_path, self.name + "_stor")):
|
||||
raise IOError("Storage doesn't exists!")
|
||||
self._f = io.open(os.path.join(
|
||||
self.db_path, self.name + "_stor"), 'r+b', buffering=0)
|
||||
self.flush()
|
||||
self._f.seek(0, 2)
|
||||
|
||||
def destroy(self):
|
||||
os.unlink(os.path.join(self.db_path, self.name + '_stor'))
|
||||
|
||||
def close(self):
|
||||
self._f.close()
|
||||
# self.flush()
|
||||
# self.fsync()
|
||||
|
||||
def data_from(self, data):
|
||||
return marshal.loads(data)
|
||||
|
||||
def data_to(self, data):
|
||||
return marshal.dumps(data)
|
||||
|
||||
def save(self, data):
|
||||
s_data = self.data_to(data)
|
||||
self._f.seek(0, 2)
|
||||
start = self._f.tell()
|
||||
size = len(s_data)
|
||||
self._f.write(s_data)
|
||||
self.flush()
|
||||
return start, size
|
||||
|
||||
def insert(self, data):
|
||||
return self.save(data)
|
||||
|
||||
def update(self, data):
|
||||
return self.save(data)
|
||||
|
||||
def get(self, start, size, status='c'):
|
||||
if status == 'd':
|
||||
return None
|
||||
else:
|
||||
self._f.seek(start)
|
||||
return self.data_from(self._f.read(size))
|
||||
|
||||
def flush(self):
|
||||
self._f.flush()
|
||||
|
||||
def fsync(self):
|
||||
os.fsync(self._f.fileno())
|
||||
|
||||
|
||||
# classes for public use, done in this way because of
|
||||
# generation static files with indexes (_index directory)
|
||||
|
||||
|
||||
class Storage(IU_Storage):
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user