Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit 15ba65c

Browse files
committed
new_version: v1.0.3
Implement single-file mode for database persistence Add support for storing all database collections in a single .mdb file instead of separate files. This includes: - Modifying MainyDB initialization to always use single-file mode - Updating collection operations to handle single-file persistence - Adding tests for single-file mode functionality
1 parent 620f8ae commit 15ba65c

File tree

2 files changed

+99
-11
lines changed

2 files changed

+99
-11
lines changed

MainyDB/core.py

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ def _save_data(self, async_write=True):
133133
'documents': self.documents,
134134
'indexes': self.indexes
135135
}
136+
# In single-file mode, persist the entire DB to the .mdb file
137+
if getattr(self.database.db, 'single_file_mode', False):
138+
self.database.db._save_single_file()
139+
return
140+
# Otherwise, persist the collection as its own .collection file
136141
if async_write:
137142
self.database.db.writer.write(self.file_path, data)
138143
else:
@@ -426,13 +431,25 @@ def bulk_write(self, operations, ordered=True):
426431
return result
427432
def drop(self):
428433
with self.lock:
434+
if getattr(self.database.db, 'single_file_mode', False):
435+
# Only clear in-memory and resave the single file
436+
self.documents = []
437+
self.indexes = {}
438+
self.database.db._save_single_file()
439+
return True
429440
if os.path.exists(self.file_path):
430441
os.remove(self.file_path)
431442
self.documents = []
432443
self.indexes = {}
433444
return True
434445
def rename(self, new_name):
435446
with self.lock:
447+
if getattr(self.database.db, 'single_file_mode', False):
448+
# Rename only in-memory and resave the single file
449+
self.database.collections[new_name] = self.database.collections.pop(self.name)
450+
self.name = new_name
451+
self.database.db._save_single_file()
452+
return True
436453
old_path = self.file_path
437454
new_path = os.path.join(os.path.dirname(old_path), f"{new_name}.collection")
438455
if os.path.exists(old_path):
@@ -505,10 +522,15 @@ def __init__(self, mainydb, name):
505522
self.name = name
506523
self.collections = {}
507524
self.path = os.path.join(self.db.path, self.name)
508-
if not os.path.exists(self.path):
509-
os.makedirs(self.path)
525+
# In single-file mode non creare directory per database
526+
if not getattr(self.db, 'single_file_mode', False):
527+
if not os.path.exists(self.path):
528+
os.makedirs(self.path)
510529
self._load_collections()
511530
def _load_collections(self):
531+
# In single-file mode, collections are loaded by MainyDB._load_single_file
532+
if getattr(self.db, 'single_file_mode', False):
533+
return
512534
if os.path.exists(self.path):
513535
for filename in os.listdir(self.path):
514536
if filename.endswith('.collection'):
@@ -521,11 +543,16 @@ def create_collection(self, name, options=None):
521543
file_path = os.path.join(self.path, f"{name}.collection")
522544
collection = Collection(self, name, file_path, options)
523545
self.collections[name] = collection
546+
# Persist immediately in single-file mode so the .mdb exists/updates
547+
if getattr(self.db, 'single_file_mode', False):
548+
self.db._save_single_file()
524549
return collection
525550
def drop_collection(self, name):
526551
if name in self.collections:
527552
self.collections[name].drop()
528553
del self.collections[name]
554+
if getattr(self.db, 'single_file_mode', False):
555+
self.db._save_single_file()
529556
return True
530557
return False
531558
def list_collection_names(self):
@@ -544,14 +571,15 @@ class MainyDB:
544571
def __init__(self, path=None, pymongo_compatible=False):
545572
if path is None:
546573
path = os.path.join(os.getcwd(), 'mainydb.mdb')
547-
if os.path.isfile(path) or not os.path.exists(path) and not path.endswith('/'):
574+
# Sempre modalità single-file: se è directory, usa <dir>/mainydb.mdb; altrimenti usa il file dato
575+
if os.path.isdir(path):
548576
self.single_file_mode = True
549-
self.path = os.path.dirname(path)
550-
self.db_file = path
551-
else:
552-
self.single_file_mode = False
553577
self.path = path
554-
self.db_file = None
578+
self.db_file = os.path.join(path, 'mainydb.mdb')
579+
else:
580+
self.single_file_mode = True
581+
self.path = os.path.dirname(path) or os.getcwd()
582+
self.db_file = path
555583
if not os.path.exists(self.path) and self.path:
556584
os.makedirs(self.path)
557585
self.pymongo_compatible = pymongo_compatible
@@ -566,9 +594,7 @@ def _load_single_file(self):
566594
if os.path.exists(self.db_file):
567595
try:
568596
with open(self.db_file, 'rb') as f:
569-
encrypted_data = f.read()
570-
decrypted_data = self.fernet.decrypt(encrypted_data)
571-
data = pickle.loads(decrypted_data)
597+
data = pickle.load(f)
572598
for db_name, collections in data.items():
573599
db = Database(self, db_name)
574600
for coll_name, coll_data in collections.items():
@@ -604,6 +630,9 @@ def get_database(self, name):
604630
if name not in self.databases:
605631
db = Database(self, name)
606632
self.databases[name] = db
633+
if self.single_file_mode:
634+
# Ensure the .mdb file is created/updated when a new DB is added
635+
self._save_single_file()
607636
return db
608637
return self.databases[name]
609638
def drop_database(self, name):
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import unittest
2+
import os
3+
import tempfile
4+
import pickle
5+
from MainyDB.core import MainyDB
6+
7+
8+
class TestSingleFileMode(unittest.TestCase):
9+
def test_mdb_only_with_directory_path(self):
10+
with tempfile.TemporaryDirectory() as tmpdir:
11+
db = MainyDB(path=tmpdir)
12+
coll = db.get_database('testdb').get_collection('items')
13+
coll.insert_one({'name': 'alpha'})
14+
db.close()
15+
16+
mdb_path = os.path.join(tmpdir, 'mainydb.mdb')
17+
self.assertTrue(os.path.isfile(mdb_path), 'mainydb.mdb should exist')
18+
# Ensure no .collection files anywhere in tmpdir
19+
found_collection = []
20+
for root, dirs, files in os.walk(tmpdir):
21+
found_collection.extend([f for f in files if f.endswith('.collection')])
22+
self.assertEqual(found_collection, [], 'No .collection files should exist')
23+
# Ensure no subdirectories created for databases in single-file mode
24+
top_level_dirs = [d for d in os.listdir(tmpdir) if os.path.isdir(os.path.join(tmpdir, d))]
25+
self.assertEqual(top_level_dirs, [], 'No database directories should be created')
26+
27+
def test_mdb_only_with_file_path(self):
28+
with tempfile.TemporaryDirectory() as tmpdir:
29+
mdb_path = os.path.join(tmpdir, 'mydb.mdb')
30+
db = MainyDB(path=mdb_path)
31+
coll = db.get_database('foo').get_collection('bar')
32+
coll.insert_one({'k': 'v'})
33+
db.close()
34+
35+
self.assertTrue(os.path.isfile(mdb_path), 'specified .mdb should exist')
36+
found_collection = []
37+
for root, dirs, files in os.walk(tmpdir):
38+
found_collection.extend([f for f in files if f.endswith('.collection')])
39+
self.assertEqual(found_collection, [], 'No .collection files should exist')
40+
41+
def test_data_persisted_in_mdb(self):
42+
with tempfile.TemporaryDirectory() as tmpdir:
43+
db = MainyDB(path=tmpdir)
44+
coll = db.get_database('persist').get_collection('docs')
45+
coll.insert_many([{'n': i} for i in range(3)])
46+
db.close()
47+
48+
mdb_path = os.path.join(tmpdir, 'mainydb.mdb')
49+
with open(mdb_path, 'rb') as f:
50+
data = pickle.load(f)
51+
self.assertIn('persist', data)
52+
self.assertIn('docs', data['persist'])
53+
docs = data['persist']['docs']['documents']
54+
self.assertEqual(len(docs), 3)
55+
self.assertTrue(any(d.get('n') == 1 for d in docs))
56+
57+
58+
if __name__ == '__main__':
59+
unittest.main()

0 commit comments

Comments
 (0)