Skip to content

Commit 952d627

Browse files
committed
finish object storage support for full alignment with tdb9
1 parent eeac5a6 commit 952d627

4 files changed

Lines changed: 281 additions & 4 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "tidesdb"
7-
version = "0.9.7"
7+
version = "0.9.8"
88
description = "Official Python bindings for TidesDB - A high-performance embedded key-value storage engine"
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/tidesdb/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
ColumnFamily,
1515
Config,
1616
ColumnFamilyConfig,
17+
ObjStoreConfig,
18+
ObjStoreBackend,
1719
Stats,
1820
CacheStats,
1921
DbStats,
@@ -24,6 +26,8 @@
2426
TidesDBError,
2527
default_config,
2628
default_column_family_config,
29+
objstore_default_config,
30+
objstore_fs_create,
2731
save_config_to_ini,
2832
load_config_from_ini,
2933
CommitOp,
@@ -45,14 +49,16 @@
4549
TDB_ERR_READONLY,
4650
)
4751

48-
__version__ = "0.9.7"
52+
__version__ = "0.9.8"
4953
__all__ = [
5054
"TidesDB",
5155
"Transaction",
5256
"Iterator",
5357
"ColumnFamily",
5458
"Config",
5559
"ColumnFamilyConfig",
60+
"ObjStoreConfig",
61+
"ObjStoreBackend",
5662
"Stats",
5763
"CacheStats",
5864
"DbStats",
@@ -63,6 +69,8 @@
6369
"TidesDBError",
6470
"default_config",
6571
"default_column_family_config",
72+
"objstore_default_config",
73+
"objstore_fs_create",
6674
"save_config_to_ini",
6775
"load_config_from_ini",
6876
"CommitOp",

src/tidesdb/tidesdb.py

Lines changed: 156 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ class IsolationLevel(IntEnum):
138138
SERIALIZABLE = 4
139139

140140

141+
class ObjStoreBackend(IntEnum):
142+
"""Object store backend types."""
143+
144+
BACKEND_FS = 0
145+
BACKEND_S3 = 1
146+
BACKEND_UNKNOWN = 99
147+
148+
141149
class TidesDBError(Exception):
142150
"""Base exception for TidesDB errors."""
143151

@@ -227,6 +235,29 @@ class _CCommitOp(Structure):
227235
COMMIT_HOOK_FUNC = CFUNCTYPE(c_int, POINTER(_CCommitOp), c_int, c_uint64, c_void_p)
228236

229237

238+
class _CObjStoreConfig(Structure):
239+
"""C structure for tidesdb_objstore_config_t."""
240+
241+
_fields_ = [
242+
("local_cache_path", c_char_p),
243+
("local_cache_max_bytes", c_size_t),
244+
("cache_on_read", c_int),
245+
("cache_on_write", c_int),
246+
("max_concurrent_uploads", c_int),
247+
("max_concurrent_downloads", c_int),
248+
("multipart_threshold", c_size_t),
249+
("multipart_part_size", c_size_t),
250+
("sync_manifest_to_object", c_int),
251+
("replicate_wal", c_int),
252+
("wal_upload_sync", c_int),
253+
("wal_sync_threshold_bytes", c_size_t),
254+
("wal_sync_on_commit", c_int),
255+
("replica_mode", c_int),
256+
("replica_sync_interval_us", c_uint64),
257+
("replica_replay_wal", c_int),
258+
]
259+
260+
230261
class _CConfig(Structure):
231262
"""C structure for tidesdb_config_t."""
232263

@@ -526,6 +557,57 @@ class _CDbStats(Structure):
526557
_lib.tidesdb_promote_to_primary.argtypes = [c_void_p]
527558
_lib.tidesdb_promote_to_primary.restype = c_int
528559

560+
_lib.tidesdb_objstore_default_config.argtypes = []
561+
_lib.tidesdb_objstore_default_config.restype = _CObjStoreConfig
562+
563+
_lib.tidesdb_objstore_fs_create.argtypes = [c_char_p]
564+
_lib.tidesdb_objstore_fs_create.restype = c_void_p
565+
566+
567+
@dataclass
568+
class ObjStoreConfig:
569+
"""Configuration for object store mode behavior."""
570+
571+
local_cache_path: str | None = None
572+
local_cache_max_bytes: int = 0
573+
cache_on_read: bool = True
574+
cache_on_write: bool = True
575+
max_concurrent_uploads: int = 4
576+
max_concurrent_downloads: int = 8
577+
multipart_threshold: int = 64 * 1024 * 1024
578+
multipart_part_size: int = 8 * 1024 * 1024
579+
sync_manifest_to_object: bool = True
580+
replicate_wal: bool = True
581+
wal_upload_sync: bool = False
582+
wal_sync_threshold_bytes: int = 1 * 1024 * 1024
583+
wal_sync_on_commit: bool = False
584+
replica_mode: bool = False
585+
replica_sync_interval_us: int = 5000000
586+
replica_replay_wal: bool = True
587+
588+
def _to_c_struct(self) -> _CObjStoreConfig:
589+
"""Convert to C structure."""
590+
c_cfg = _CObjStoreConfig()
591+
c_cfg.local_cache_path = (
592+
self.local_cache_path.encode("utf-8") if self.local_cache_path else None
593+
)
594+
c_cfg.local_cache_max_bytes = self.local_cache_max_bytes
595+
c_cfg.cache_on_read = 1 if self.cache_on_read else 0
596+
c_cfg.cache_on_write = 1 if self.cache_on_write else 0
597+
c_cfg.max_concurrent_uploads = self.max_concurrent_uploads
598+
c_cfg.max_concurrent_downloads = self.max_concurrent_downloads
599+
c_cfg.multipart_threshold = self.multipart_threshold
600+
c_cfg.multipart_part_size = self.multipart_part_size
601+
c_cfg.sync_manifest_to_object = 1 if self.sync_manifest_to_object else 0
602+
c_cfg.replicate_wal = 1 if self.replicate_wal else 0
603+
c_cfg.wal_upload_sync = 1 if self.wal_upload_sync else 0
604+
c_cfg.wal_sync_threshold_bytes = self.wal_sync_threshold_bytes
605+
c_cfg.wal_sync_on_commit = 1 if self.wal_sync_on_commit else 0
606+
c_cfg.replica_mode = 1 if self.replica_mode else 0
607+
c_cfg.replica_sync_interval_us = self.replica_sync_interval_us
608+
c_cfg.replica_replay_wal = 1 if self.replica_replay_wal else 0
609+
return c_cfg
610+
529611

530612
@dataclass
531613
class Config:
@@ -546,6 +628,8 @@ class Config:
546628
unified_memtable_skip_list_probability: float = 0.0
547629
unified_memtable_sync_mode: SyncMode = SyncMode.SYNC_NONE
548630
unified_memtable_sync_interval_us: int = 0
631+
object_store: c_void_p | None = None
632+
object_store_config: ObjStoreConfig | None = None
549633

550634

551635
@dataclass
@@ -704,6 +788,57 @@ def default_config() -> Config:
704788
return Config(db_path="")
705789

706790

791+
def objstore_default_config() -> ObjStoreConfig:
792+
"""Get default object store configuration from C library."""
793+
c_cfg = _lib.tidesdb_objstore_default_config()
794+
path = None
795+
if c_cfg.local_cache_path:
796+
try:
797+
path = c_cfg.local_cache_path.decode("utf-8")
798+
except (UnicodeDecodeError, ValueError):
799+
path = None
800+
return ObjStoreConfig(
801+
local_cache_path=path,
802+
local_cache_max_bytes=c_cfg.local_cache_max_bytes,
803+
cache_on_read=bool(c_cfg.cache_on_read),
804+
cache_on_write=bool(c_cfg.cache_on_write),
805+
max_concurrent_uploads=c_cfg.max_concurrent_uploads,
806+
max_concurrent_downloads=c_cfg.max_concurrent_downloads,
807+
multipart_threshold=c_cfg.multipart_threshold,
808+
multipart_part_size=c_cfg.multipart_part_size,
809+
sync_manifest_to_object=bool(c_cfg.sync_manifest_to_object),
810+
replicate_wal=bool(c_cfg.replicate_wal),
811+
wal_upload_sync=bool(c_cfg.wal_upload_sync),
812+
wal_sync_threshold_bytes=c_cfg.wal_sync_threshold_bytes,
813+
wal_sync_on_commit=bool(c_cfg.wal_sync_on_commit),
814+
replica_mode=bool(c_cfg.replica_mode),
815+
replica_sync_interval_us=c_cfg.replica_sync_interval_us,
816+
replica_replay_wal=bool(c_cfg.replica_replay_wal),
817+
)
818+
819+
820+
def objstore_fs_create(root_dir: str) -> c_void_p:
821+
"""
822+
Create a filesystem-backed object store connector.
823+
824+
Stores objects as files under root_dir mirroring the key path structure.
825+
Useful for testing and local replication.
826+
827+
Args:
828+
root_dir: Directory to store objects in
829+
830+
Returns:
831+
Opaque object store handle for use in Config.object_store
832+
833+
Raises:
834+
TidesDBError: If creation fails
835+
"""
836+
handle = _lib.tidesdb_objstore_fs_create(root_dir.encode("utf-8"))
837+
if not handle:
838+
raise TidesDBError("failed to create filesystem object store connector", TDB_ERR_IO)
839+
return handle
840+
841+
707842
def default_column_family_config() -> ColumnFamilyConfig:
708843
"""Get default column family configuration from C library."""
709844
c_config = _lib.tidesdb_default_column_family_config()
@@ -1424,12 +1559,25 @@ def __init__(self, config: Config) -> None:
14241559
"""
14251560
self._db: c_void_p | None = None
14261561
self._closed = False
1562+
self._objstore_config_ref: _CObjStoreConfig | None = None
14271563

14281564
os.makedirs(config.db_path, exist_ok=True)
14291565
abs_path = os.path.abspath(config.db_path)
14301566

14311567
self._path_bytes = abs_path.encode("utf-8")
14321568

1569+
obj_store_ptr = None
1570+
obj_store_config_ptr = None
1571+
1572+
if config.object_store is not None:
1573+
obj_store_ptr = config.object_store
1574+
1575+
if config.object_store_config is not None:
1576+
self._objstore_config_ref = config.object_store_config._to_c_struct()
1577+
obj_store_config_ptr = ctypes.cast(
1578+
ctypes.pointer(self._objstore_config_ref), c_void_p
1579+
)
1580+
14331581
c_config = _CConfig(
14341582
db_path=self._path_bytes,
14351583
num_flush_threads=config.num_flush_threads,
@@ -1446,8 +1594,8 @@ def __init__(self, config: Config) -> None:
14461594
unified_memtable_skip_list_probability=config.unified_memtable_skip_list_probability,
14471595
unified_memtable_sync_mode=int(config.unified_memtable_sync_mode),
14481596
unified_memtable_sync_interval_us=config.unified_memtable_sync_interval_us,
1449-
object_store=None,
1450-
object_store_config=None,
1597+
object_store=obj_store_ptr,
1598+
object_store_config=obj_store_config_ptr,
14511599
)
14521600

14531601
db_ptr = c_void_p()
@@ -1476,6 +1624,8 @@ def open(
14761624
unified_memtable_skip_list_probability: float = 0.0,
14771625
unified_memtable_sync_mode: SyncMode = SyncMode.SYNC_NONE,
14781626
unified_memtable_sync_interval_us: int = 0,
1627+
object_store: c_void_p | None = None,
1628+
object_store_config: ObjStoreConfig | None = None,
14791629
) -> TidesDB:
14801630
"""
14811631
Convenience method to open a database with individual parameters.
@@ -1496,6 +1646,8 @@ def open(
14961646
unified_memtable_skip_list_probability: Skip list probability for unified memtable
14971647
unified_memtable_sync_mode: Sync mode for unified memtable WAL
14981648
unified_memtable_sync_interval_us: Sync interval in microseconds for unified memtable
1649+
object_store: Object store connector handle (from objstore_fs_create())
1650+
object_store_config: Object store behavior configuration
14991651
15001652
Returns:
15011653
TidesDB instance
@@ -1516,6 +1668,8 @@ def open(
15161668
unified_memtable_skip_list_probability=unified_memtable_skip_list_probability,
15171669
unified_memtable_sync_mode=unified_memtable_sync_mode,
15181670
unified_memtable_sync_interval_us=unified_memtable_sync_interval_us,
1671+
object_store=object_store,
1672+
object_store_config=object_store_config,
15191673
)
15201674
return cls(config)
15211675

0 commit comments

Comments
 (0)