hadro 0.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hadro-0.0.4/LICENSE +21 -0
- hadro-0.0.4/NOTICES +5 -0
- hadro-0.0.4/PKG-INFO +42 -0
- hadro-0.0.4/README.md +30 -0
- hadro-0.0.4/hadro/__init__.py +3 -0
- hadro-0.0.4/hadro/config.py +24 -0
- hadro-0.0.4/hadro/engine.py +197 -0
- hadro-0.0.4/hadro/schema.py +79 -0
- hadro-0.0.4/hadro/version.py +2 -0
- hadro-0.0.4/hadro.egg-info/PKG-INFO +42 -0
- hadro-0.0.4/hadro.egg-info/SOURCES.txt +16 -0
- hadro-0.0.4/hadro.egg-info/dependency_links.txt +1 -0
- hadro-0.0.4/hadro.egg-info/requires.txt +2 -0
- hadro-0.0.4/hadro.egg-info/top_level.txt +1 -0
- hadro-0.0.4/pyproject.toml +11 -0
- hadro-0.0.4/setup.cfg +4 -0
- hadro-0.0.4/setup.py +37 -0
- hadro-0.0.4/tests/test_interface.py +90 -0
hadro-0.0.4/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Justin Joyce
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
hadro-0.0.4/NOTICES
ADDED
hadro-0.0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: hadro
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Storage Engine
|
|
5
|
+
Home-page: https://github.com/mabel-dev/hadrodb/
|
|
6
|
+
Author: @joocer
|
|
7
|
+
Author-email: justin.joyce@joocer.com
|
|
8
|
+
Maintainer: @joocer
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICES
|
|
12
|
+
|
|
13
|
+
<div align="center">
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
HadroDB is a database storage engine for [Opteryx](https://opteryx.dev).
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/hadrodb/)
|
|
20
|
+
[](https://codecov.io/gh/mabel-dev/hadrodb)
|
|
21
|
+
[](https://pepy.tech/project/hadrodb)
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
[](https://github.com/mabel-dev/hadrodb/blob/main/LICENSE)
|
|
28
|
+
[](https://github.com/mabel-dev/hadrodb/blob/main/NOTICES)
|
|
29
|
+
|
|
30
|
+
## Status
|
|
31
|
+
|
|
32
|
+
[](https://github.com/mabel-dev/opteryx)
|
|
33
|
+
|
|
34
|
+
HadroDB is in alpha. Alpha means different things to different people, to us, being alpha means:
|
|
35
|
+
|
|
36
|
+
- Interfaces may be significantly changed
|
|
37
|
+
- The data file format is unstable
|
|
38
|
+
- Expected functionality is missing
|
|
39
|
+
- Things that worked yesterday, don't work today
|
|
40
|
+
- The results of the system may be unreliable
|
|
41
|
+
|
|
42
|
+
As such, we really don't recommend using HadroDB anywhere where your data matters.
|
hadro-0.0.4/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
HadroDB is a database storage engine for [Opteryx](https://opteryx.dev).
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/hadrodb/)
|
|
8
|
+
[](https://codecov.io/gh/mabel-dev/hadrodb)
|
|
9
|
+
[](https://pepy.tech/project/hadrodb)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
## License
|
|
14
|
+
|
|
15
|
+
[](https://github.com/mabel-dev/hadrodb/blob/main/LICENSE)
|
|
16
|
+
[](https://github.com/mabel-dev/hadrodb/blob/main/NOTICES)
|
|
17
|
+
|
|
18
|
+
## Status
|
|
19
|
+
|
|
20
|
+
[](https://github.com/mabel-dev/opteryx)
|
|
21
|
+
|
|
22
|
+
HadroDB is in alpha. Alpha means different things to different people, to us, being alpha means:
|
|
23
|
+
|
|
24
|
+
- Interfaces may be significantly changed
|
|
25
|
+
- The data file format is unstable
|
|
26
|
+
- Expected functionality is missing
|
|
27
|
+
- Things that worked yesterday, don't work today
|
|
28
|
+
- The results of the system may be unreliable
|
|
29
|
+
|
|
30
|
+
As such, we really don't recommend using HadroDB anywhere where your data matters.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from enum import auto
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ConsistencyMode(int, Enum):
|
|
6
|
+
"""
|
|
7
|
+
Aggressive will ensure each write is flushed before moving to the next, Relaxed
|
|
8
|
+
allows the OS to handle write flushes.
|
|
9
|
+
|
|
10
|
+
Aggressive has significant impact on write performance, but significantly reduces
|
|
11
|
+
the chance of data loss due to system crashes.
|
|
12
|
+
|
|
13
|
+
Relaxed is faster (10x), but if the system crashes, your data may be in an
|
|
14
|
+
inconsistent state.
|
|
15
|
+
|
|
16
|
+
Aggressive is the default (safe default), but there are some scenarios where a
|
|
17
|
+
relaxed approach to write consistency is okay or acceptable.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
AGGRESSIVE = auto()
|
|
21
|
+
RELAXED = auto()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
WRITE_CONSISTENCY: ConsistencyMode = ConsistencyMode.RELAXED
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
Typical usage example:
|
|
4
|
+
|
|
5
|
+
disk: DiskStorage = DiskStore(file_name="books.db")
|
|
6
|
+
disk.set(key="othello", value="shakespeare")
|
|
7
|
+
author: str = disk.get("othello")
|
|
8
|
+
# it also supports dictionary style API too:
|
|
9
|
+
disk["hamlet"] = "shakespeare"
|
|
10
|
+
"""
|
|
11
|
+
import io
|
|
12
|
+
import os.path
|
|
13
|
+
import struct
|
|
14
|
+
import typing
|
|
15
|
+
from collections import namedtuple
|
|
16
|
+
|
|
17
|
+
from hadro.config import WRITE_CONSISTENCY
|
|
18
|
+
from hadro.config import ConsistencyMode
|
|
19
|
+
from orso import logging
|
|
20
|
+
from orso.row import Row
|
|
21
|
+
|
|
22
|
+
logging.set_log_name("MESOS")
|
|
23
|
+
logger = logging.get_logger()
|
|
24
|
+
logger.setLevel(5)
|
|
25
|
+
|
|
26
|
+
DELETED_FLAG: int = 1
|
|
27
|
+
|
|
28
|
+
RecordHeader = namedtuple("RecordHeader", ["flags", "size"])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# DiskStorage is a Log-Structured Hash Table as described in the BitCask paper. We
|
|
32
|
+
# keep appending the data to a file, like a log. DiskStorage maintains an in-memory
|
|
33
|
+
# hash table called KeyDir, which keeps the row's location on the disk.
|
|
34
|
+
#
|
|
35
|
+
# The idea is simple yet brilliant:
|
|
36
|
+
# - Write the record to the disk
|
|
37
|
+
# - Update the internal hash table to point to that byte offset
|
|
38
|
+
# - Whenever we get a read request, check the internal hash table for the address,
|
|
39
|
+
# fetch that and return
|
|
40
|
+
#
|
|
41
|
+
# KeyDir does not store values, only their locations.
|
|
42
|
+
#
|
|
43
|
+
# The above approach solves a lot of problems:
|
|
44
|
+
# - Writes are insanely fast since you are just appending to the file
|
|
45
|
+
# - Reads are insanely fast since you do only one disk seek. In B-Tree backed
|
|
46
|
+
# storage, there could be 2-3 disk seeks
|
|
47
|
+
#
|
|
48
|
+
# However, there are drawbacks too:
|
|
49
|
+
# - We need to maintain an in-memory hash table KeyDir. A database with a large
|
|
50
|
+
# number of keys would require more RAM
|
|
51
|
+
# - Since we need to build the KeyDir at initialisation, it will affect the startup
|
|
52
|
+
# time too
|
|
53
|
+
# - Deleted keys need to be purged from the file to reduce the file size
|
|
54
|
+
#
|
|
55
|
+
# Read the paper for more details: https://riak.com/assets/bitcask-intro.pdf
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class HadroDB:
|
|
59
|
+
"""
|
|
60
|
+
Implements the KV store on the disk
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
file_name (str): name of the file where all the data will be written. Just
|
|
64
|
+
passing the file name will save the data in the current directory. You may
|
|
65
|
+
pass the full file location too.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
file_name (str): name of the file where all the data will be written. Just
|
|
69
|
+
passing the file name will save the data in the current directory. You may
|
|
70
|
+
pass the full file location too.
|
|
71
|
+
file (typing.BinaryIO): file object pointing the file_name
|
|
72
|
+
write_position (int): current cursor position in the file where the data can be
|
|
73
|
+
written
|
|
74
|
+
key_dir (dict[str, KeyEntry]): is a map of key and KeyEntry being the value.
|
|
75
|
+
KeyEntry contains the position of the byte offset in the file where the
|
|
76
|
+
value exists. key_dir map acts as in-memory index to fetch the values
|
|
77
|
+
quickly from the disk
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, collection: typing.Union[str, None] = None):
|
|
81
|
+
logger.warning("HadroDB is experimental and not recommended for use.")
|
|
82
|
+
self.collection: str = collection
|
|
83
|
+
self.file_name: str = collection + "/00000000.data"
|
|
84
|
+
self._schema_file: str = collection + "/00000000.schema"
|
|
85
|
+
self.write_position: int = 0
|
|
86
|
+
self.key_dir: dict[bytes, Row] = {}
|
|
87
|
+
|
|
88
|
+
if collection is None:
|
|
89
|
+
raise ValueError("HadroDB requires a collection name")
|
|
90
|
+
# if the collection exists, it must be a folder, not a file
|
|
91
|
+
if os.path.exists(collection):
|
|
92
|
+
if not os.path.isdir(collection):
|
|
93
|
+
raise ValueError("Collection must be a folder")
|
|
94
|
+
# if the file exists already, then we will load the key_dir
|
|
95
|
+
# self._init_key_dir()
|
|
96
|
+
else:
|
|
97
|
+
os.makedirs(collection, exist_ok=True)
|
|
98
|
+
|
|
99
|
+
# if os.path.exists(self._schema_file):
|
|
100
|
+
# load the schema
|
|
101
|
+
|
|
102
|
+
# we open the file in `a+b` mode:
|
|
103
|
+
# a - says the writes are append only. `a+` means we want append and read
|
|
104
|
+
# b - says that we are operating the file in binary mode (as opposed to the
|
|
105
|
+
# default string mode)
|
|
106
|
+
self.file: typing.BinaryIO = open(self.file_name, "a+b")
|
|
107
|
+
self.fileno = self.file.fileno()
|
|
108
|
+
|
|
109
|
+
schema = {
|
|
110
|
+
"id": {"type": "SMALLINT", "nullable": False},
|
|
111
|
+
"planetId": {"type": "SMALLINT", "nullable": False},
|
|
112
|
+
"name": {"type": "VARCHAR", "nullable": False},
|
|
113
|
+
"gm": {"type": "FLOAT", "nullable": False},
|
|
114
|
+
"radius": {"type": "FLOAT", "nullable": False},
|
|
115
|
+
"density": {"type": "FLOAT", "nullable": True},
|
|
116
|
+
"magnitude": {"type": "FLOAT", "nullable": True},
|
|
117
|
+
"albedo": {"type": "FLOAT", "nullable": True},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
self.rows = Row.create_class(schema)
|
|
121
|
+
|
|
122
|
+
def append(self, record) -> None:
|
|
123
|
+
if isinstance(record, dict):
|
|
124
|
+
_record = record.values()
|
|
125
|
+
else:
|
|
126
|
+
_record = record
|
|
127
|
+
|
|
128
|
+
record = self.rows(_record)
|
|
129
|
+
# test it matches the schema
|
|
130
|
+
|
|
131
|
+
bytes_to_write = record.to_bytes()
|
|
132
|
+
self._write(bytes_to_write)
|
|
133
|
+
|
|
134
|
+
# update indices index
|
|
135
|
+
#
|
|
136
|
+
|
|
137
|
+
self.write_position += len(bytes_to_write)
|
|
138
|
+
|
|
139
|
+
def scan(self, columns=None, predicates=None):
|
|
140
|
+
block_size: int = 8 * 1024 * 1024 # read 1Mb at a time
|
|
141
|
+
self.file.seek(0, 0)
|
|
142
|
+
|
|
143
|
+
# TODO: read file header
|
|
144
|
+
|
|
145
|
+
buffer = io.BufferedReader(self.file, block_size) # type: ignore
|
|
146
|
+
|
|
147
|
+
header_bytes = buffer.read(5)
|
|
148
|
+
flags, size = struct.unpack(">BI", header_bytes)
|
|
149
|
+
block_start = 5 # start of the current block
|
|
150
|
+
|
|
151
|
+
while size > 0:
|
|
152
|
+
if block_start + size > block_size:
|
|
153
|
+
# The current record spans multiple blocks, so read the rest of it in the next block
|
|
154
|
+
remaining_size = size - (block_size - block_start)
|
|
155
|
+
data_bytes = bytearray(
|
|
156
|
+
buffer.read(block_size - block_start)
|
|
157
|
+
) # read the remaining bytes in the current block
|
|
158
|
+
while remaining_size > 0:
|
|
159
|
+
# Read the remaining bytes in subsequent blocks
|
|
160
|
+
block_bytes = buffer.read(min(remaining_size, block_size))
|
|
161
|
+
data_bytes += block_bytes
|
|
162
|
+
remaining_size -= len(block_bytes)
|
|
163
|
+
block_start = len(block_bytes)
|
|
164
|
+
else:
|
|
165
|
+
# The current record fits in the current block, so just read it
|
|
166
|
+
data_bytes = bytearray(buffer.read(size))
|
|
167
|
+
block_start += size
|
|
168
|
+
|
|
169
|
+
if flags & DELETED_FLAG == 0:
|
|
170
|
+
yield self.rows.from_bytes(data_bytes)
|
|
171
|
+
|
|
172
|
+
# Read the size of the next record
|
|
173
|
+
header_bytes = buffer.read(5)
|
|
174
|
+
if len(header_bytes) == 0:
|
|
175
|
+
break
|
|
176
|
+
flags, size = struct.unpack(">BI", header_bytes)
|
|
177
|
+
block_start += 5 # add the size of the size field to the start of the next block
|
|
178
|
+
|
|
179
|
+
def _write(self, data: bytes) -> None:
|
|
180
|
+
# saving stuff to a file reliably is hard!
|
|
181
|
+
# if you would like to explore and learn more, then
|
|
182
|
+
# start from here: https://danluu.com/file-consistency/
|
|
183
|
+
# and read this too: https://lwn.net/Articles/457667/
|
|
184
|
+
os.write(self.fileno, data)
|
|
185
|
+
|
|
186
|
+
if WRITE_CONSISTENCY == ConsistencyMode.AGGRESSIVE:
|
|
187
|
+
# calling fsync after every write is important, this assures that our writes
|
|
188
|
+
# are actually persisted to the disk
|
|
189
|
+
os.fsync(self.fileno)
|
|
190
|
+
|
|
191
|
+
def close(self) -> None:
|
|
192
|
+
# before we close the file, we need to safely write the contents in the buffers
|
|
193
|
+
# to the disk. Check documentation of DiskStorage._write() to understand
|
|
194
|
+
# following the operations
|
|
195
|
+
self.file.flush()
|
|
196
|
+
os.fsync(self.fileno)
|
|
197
|
+
self.file.close()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
class Field:
|
|
2
|
+
def __init__(self, name, ftype, nullable=True):
|
|
3
|
+
self.name = name
|
|
4
|
+
self.ftype = ftype
|
|
5
|
+
self.nullable = nullable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Index:
|
|
9
|
+
def __init__(self, name, fields):
|
|
10
|
+
self.name = name
|
|
11
|
+
self.fields = fields
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TableSchema:
|
|
15
|
+
def __init__(self, fields=None, primary_key=None, indexes=None):
|
|
16
|
+
self._fields = {}
|
|
17
|
+
self.primary_key = primary_key
|
|
18
|
+
self.indexes = indexes or []
|
|
19
|
+
|
|
20
|
+
if fields is not None:
|
|
21
|
+
for name, field in fields.items():
|
|
22
|
+
self.add_field(name, field["type"], field.get("nullable", True))
|
|
23
|
+
|
|
24
|
+
def add_field(self, name_or_field, field_type=None, nullable=True):
|
|
25
|
+
if isinstance(name_or_field, Field):
|
|
26
|
+
name = name_or_field.name
|
|
27
|
+
field = name_or_field
|
|
28
|
+
else:
|
|
29
|
+
name = name_or_field
|
|
30
|
+
field = Field(name, field_type, nullable)
|
|
31
|
+
self._fields[name] = field
|
|
32
|
+
|
|
33
|
+
def remove_field(self, name):
|
|
34
|
+
if name in self._fields:
|
|
35
|
+
del self._fields[name]
|
|
36
|
+
|
|
37
|
+
def field(self, i):
|
|
38
|
+
if isinstance(i, int):
|
|
39
|
+
return self._fields[list(self._fields.keys())[i]]
|
|
40
|
+
else:
|
|
41
|
+
return self._fields[i]
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def num_fields(self):
|
|
45
|
+
return len(self._fields)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def names(self):
|
|
49
|
+
return list(self._fields.keys())
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def types(self):
|
|
53
|
+
return [field["type"] for field in self._fields.values()]
|
|
54
|
+
|
|
55
|
+
def to_dict(self):
|
|
56
|
+
return {
|
|
57
|
+
"fields": {
|
|
58
|
+
name: {"type": field["type"], "nullable": field["nullable"]}
|
|
59
|
+
for name, field in self._fields.items()
|
|
60
|
+
},
|
|
61
|
+
"primaryKey": self.primary_key,
|
|
62
|
+
"indexes": [index.to_dict() for index in self.indexes],
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def validate(self, data):
|
|
66
|
+
if not isinstance(data, dict):
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
# Check that all required fields are present
|
|
70
|
+
for field_name, field in self._fields.items():
|
|
71
|
+
if not field["nullable"] and field_name not in data:
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
# Check that all fields have the correct type
|
|
75
|
+
for field_name, field in self._fields.items():
|
|
76
|
+
if field_name in data and not isinstance(data[field_name], field["type"]):
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
return True
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: hadro
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Storage Engine
|
|
5
|
+
Home-page: https://github.com/mabel-dev/hadrodb/
|
|
6
|
+
Author: @joocer
|
|
7
|
+
Author-email: justin.joyce@joocer.com
|
|
8
|
+
Maintainer: @joocer
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICES
|
|
12
|
+
|
|
13
|
+
<div align="center">
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
HadroDB is a database storage engine for [Opteryx](https://opteryx.dev).
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/hadrodb/)
|
|
20
|
+
[](https://codecov.io/gh/mabel-dev/hadrodb)
|
|
21
|
+
[](https://pepy.tech/project/hadrodb)
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
[](https://github.com/mabel-dev/hadrodb/blob/main/LICENSE)
|
|
28
|
+
[](https://github.com/mabel-dev/hadrodb/blob/main/NOTICES)
|
|
29
|
+
|
|
30
|
+
## Status
|
|
31
|
+
|
|
32
|
+
[](https://github.com/mabel-dev/opteryx)
|
|
33
|
+
|
|
34
|
+
HadroDB is in alpha. Alpha means different things to different people, to us, being alpha means:
|
|
35
|
+
|
|
36
|
+
- Interfaces may be significantly changed
|
|
37
|
+
- The data file format is unstable
|
|
38
|
+
- Expected functionality is missing
|
|
39
|
+
- Things that worked yesterday, don't work today
|
|
40
|
+
- The results of the system may be unreliable
|
|
41
|
+
|
|
42
|
+
As such, we really don't recommend using HadroDB anywhere where your data matters.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
NOTICES
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.py
|
|
6
|
+
hadro/__init__.py
|
|
7
|
+
hadro/config.py
|
|
8
|
+
hadro/engine.py
|
|
9
|
+
hadro/schema.py
|
|
10
|
+
hadro/version.py
|
|
11
|
+
hadro.egg-info/PKG-INFO
|
|
12
|
+
hadro.egg-info/SOURCES.txt
|
|
13
|
+
hadro.egg-info/dependency_links.txt
|
|
14
|
+
hadro.egg-info/requires.txt
|
|
15
|
+
hadro.egg-info/top_level.txt
|
|
16
|
+
tests/test_interface.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hadro
|
hadro-0.0.4/setup.cfg
ADDED
hadro-0.0.4/setup.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from setuptools import find_packages
|
|
2
|
+
from setuptools import setup
|
|
3
|
+
|
|
4
|
+
LIBRARY = "hadro"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__version__ = "notset"
|
|
8
|
+
with open(f"{LIBRARY}/version.py", mode="r") as v:
|
|
9
|
+
vers = v.read()
|
|
10
|
+
exec(vers) # nosec
|
|
11
|
+
|
|
12
|
+
with open("README.md", mode="r", encoding="UTF8") as rm:
|
|
13
|
+
long_description = rm.read()
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
with open("requirements.txt", "r") as f:
|
|
17
|
+
required = f.read().splitlines()
|
|
18
|
+
except:
|
|
19
|
+
with open(f"{LIBRARY}.egg-info/requires.txt", "r") as f:
|
|
20
|
+
required = f.read().splitlines()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
setup_config = {
|
|
24
|
+
"name": LIBRARY,
|
|
25
|
+
"version": __version__,
|
|
26
|
+
"description": "Storage Engine",
|
|
27
|
+
"long_description": long_description,
|
|
28
|
+
"long_description_content_type": "text/markdown",
|
|
29
|
+
"maintainer": "@joocer",
|
|
30
|
+
"author": "@joocer",
|
|
31
|
+
"author_email": "justin.joyce@joocer.com",
|
|
32
|
+
"packages": find_packages(include=[LIBRARY, f"{LIBRARY}.*"]),
|
|
33
|
+
"url": "https://github.com/mabel-dev/hadrodb/",
|
|
34
|
+
"install_requires": required,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setup(**setup_config)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
sys.path.insert(1, os.path.join(sys.path[0], ".."))
|
|
7
|
+
sys.path.insert(1, os.path.join(sys.path[0], "../.."))
|
|
8
|
+
|
|
9
|
+
from hadro import HadroDB # isort: skip
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
TEST_COLLECTION = "test_123"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_doc(docs):
|
|
16
|
+
docid = str(time.monotonic_ns())
|
|
17
|
+
doc = {"document": docid}
|
|
18
|
+
docs[docid] = doc
|
|
19
|
+
return docid, doc
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_interface():
|
|
23
|
+
# initialize the dataset
|
|
24
|
+
if os.path.exists(TEST_COLLECTION): # pragma: no cover
|
|
25
|
+
shutil.rmtree(TEST_COLLECTION, ignore_errors=True)
|
|
26
|
+
|
|
27
|
+
# create a copy of the docs for us to compare against
|
|
28
|
+
comparision_set_of_docs = {}
|
|
29
|
+
|
|
30
|
+
# OPEN COLLECTION
|
|
31
|
+
hadro = HadroDB(TEST_COLLECTION)
|
|
32
|
+
|
|
33
|
+
# SET DOCUMENT IN COLLECTION
|
|
34
|
+
# subscript syntax
|
|
35
|
+
docid, doc = create_doc(comparision_set_of_docs)
|
|
36
|
+
hadro.append(doc)
|
|
37
|
+
|
|
38
|
+
docid, doc = create_doc(comparision_set_of_docs)
|
|
39
|
+
# set syntax
|
|
40
|
+
hadro.append(doc)
|
|
41
|
+
|
|
42
|
+
# add syntax
|
|
43
|
+
docid = hadro.append(doc)
|
|
44
|
+
comparision_set_of_docs[docid] = doc
|
|
45
|
+
|
|
46
|
+
# GET DOCUMENT FROM COLLECTION
|
|
47
|
+
# subscript, single doc syntax
|
|
48
|
+
# doc = hadro[docid]
|
|
49
|
+
# assert doc == comparision_set_of_docs[docid]
|
|
50
|
+
|
|
51
|
+
# subscript, list syntax
|
|
52
|
+
# list_of_docs = hadro[comparision_set_of_docs.keys()]
|
|
53
|
+
# assert len(list_of_docs) == len(comparision_set_of_docs)
|
|
54
|
+
|
|
55
|
+
# get syntax
|
|
56
|
+
# doc = hadro.get(docid)
|
|
57
|
+
# assert doc == comparision_set_of_docs[docid]
|
|
58
|
+
|
|
59
|
+
# GET COLLECTION SIZE
|
|
60
|
+
# assert len(hadro) == len(comparision_set_of_docs)
|
|
61
|
+
|
|
62
|
+
# GET KEYS IN COLLECTION
|
|
63
|
+
# keys = hadro.keys()
|
|
64
|
+
# assert len(keys) == len(comparision_set_of_docs)
|
|
65
|
+
# for key in keys:
|
|
66
|
+
# assert hadro[key] == comparision_set_of_docs[key]
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
# DELETE DOCUMENT FROM COLLECTION
|
|
70
|
+
del hadro[id]
|
|
71
|
+
hadro.del(id[, id...])
|
|
72
|
+
|
|
73
|
+
# CONTAINMENT
|
|
74
|
+
id in hadro
|
|
75
|
+
hadro.contains(id[, id ...])
|
|
76
|
+
|
|
77
|
+
# FILTER COLLECTION
|
|
78
|
+
filtered_collection = hadro.where(predicate)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
hadro.close()
|
|
84
|
+
|
|
85
|
+
# tidy up
|
|
86
|
+
if os.path.exists(TEST_COLLECTION): # pragma: no cover
|
|
87
|
+
shutil.rmtree(TEST_COLLECTION, ignore_errors=True)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
test_interface()
|