Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,3 @@ Multiple licenses apply:
## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## Support

- [Discord](https://discord.gg/tWEmjR66cy)
- [GitHub Issues](https://github.com/tidesdb/tidesdb-python/issues)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "tidesdb"
version = "0.9.8"
version = "0.10.0"
description = "Official Python bindings for TidesDB - A high-performance embedded key-value storage engine"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion src/tidesdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
TDB_ERR_READONLY,
)

__version__ = "0.9.8"
__version__ = "0.10.0"
__all__ = [
"TidesDB",
"Transaction",
Expand Down
40 changes: 34 additions & 6 deletions src/tidesdb/tidesdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ class _CColumnFamilyConfig(Structure):
("use_btree", c_int),
("commit_hook_fn", c_void_p),
("commit_hook_ctx", c_void_p),
("object_target_file_size", c_size_t),
("_object_target_file_size_reserved", c_size_t),
("object_lazy_compaction", c_int),
("object_prefetch_compaction", c_int),
]
Expand Down Expand Up @@ -411,6 +411,9 @@ class _CDbStats(Structure):
_lib.tidesdb_txn_delete.argtypes = [c_void_p, c_void_p, POINTER(c_uint8), c_size_t]
_lib.tidesdb_txn_delete.restype = c_int

_lib.tidesdb_txn_single_delete.argtypes = [c_void_p, c_void_p, POINTER(c_uint8), c_size_t]
_lib.tidesdb_txn_single_delete.restype = c_int

_lib.tidesdb_txn_commit.argtypes = [c_void_p]
_lib.tidesdb_txn_commit.restype = c_int

Expand Down Expand Up @@ -657,7 +660,6 @@ class ColumnFamilyConfig:
l1_file_count_trigger: int = 4
l0_queue_stall_threshold: int = 20
use_btree: bool = False
object_target_file_size: int = 0
object_lazy_compaction: bool = False
object_prefetch_compaction: bool = True

Expand Down Expand Up @@ -690,7 +692,6 @@ def _to_c_struct(self, name: str = "") -> _CColumnFamilyConfig:
c_config.l1_file_count_trigger = self.l1_file_count_trigger
c_config.l0_queue_stall_threshold = self.l0_queue_stall_threshold
c_config.use_btree = 1 if self.use_btree else 0
c_config.object_target_file_size = self.object_target_file_size
c_config.object_lazy_compaction = 1 if self.object_lazy_compaction else 0
c_config.object_prefetch_compaction = 1 if self.object_prefetch_compaction else 0

Expand Down Expand Up @@ -864,7 +865,6 @@ def default_column_family_config() -> ColumnFamilyConfig:
l1_file_count_trigger=c_config.l1_file_count_trigger,
l0_queue_stall_threshold=c_config.l0_queue_stall_threshold,
use_btree=bool(c_config.use_btree),
object_target_file_size=c_config.object_target_file_size,
object_lazy_compaction=bool(c_config.object_lazy_compaction),
object_prefetch_compaction=bool(c_config.object_prefetch_compaction),
)
Expand Down Expand Up @@ -927,7 +927,6 @@ def load_config_from_ini(file_path: str, cf_name: str) -> ColumnFamilyConfig:
l1_file_count_trigger=c_config.l1_file_count_trigger,
l0_queue_stall_threshold=c_config.l0_queue_stall_threshold,
use_btree=bool(c_config.use_btree),
object_target_file_size=c_config.object_target_file_size,
object_lazy_compaction=bool(c_config.object_lazy_compaction),
object_prefetch_compaction=bool(c_config.object_prefetch_compaction),
)
Expand Down Expand Up @@ -1304,7 +1303,6 @@ def get_stats(self) -> Stats:
l1_file_count_trigger=c_cfg.l1_file_count_trigger,
l0_queue_stall_threshold=c_cfg.l0_queue_stall_threshold,
use_btree=bool(c_cfg.use_btree),
object_target_file_size=c_cfg.object_target_file_size,
object_lazy_compaction=bool(c_cfg.object_lazy_compaction),
object_prefetch_compaction=bool(c_cfg.object_prefetch_compaction),
)
Expand Down Expand Up @@ -1424,6 +1422,36 @@ def delete(self, cf: ColumnFamily, key: bytes) -> None:
if result != TDB_SUCCESS:
raise TidesDBError.from_code(result, "failed to delete key")

def single_delete(self, cf: ColumnFamily, key: bytes) -> None:
"""
Write a tombstone carrying a caller-provided promise that the key has been
put at most once since its previous single-delete (or since the start of
history). This lets compaction drop the put and the tombstone together as
soon as both appear in the same merge input, rather than carrying the
tombstone forward to the largest active level. Read semantics match delete().

The engine does not verify the promise at runtime; violating it can leave
older puts visible and is a bug in the caller. Use only for insert-once-
then-delete patterns (classic insert benchmarks, secondary indexes on
never-updated columns, log tables with scheduled purges). Not safe for
repeated updates to the same key. When in doubt, prefer delete().

Args:
cf: Column family handle
key: Key as bytes
"""
if self._closed:
raise TidesDBError("Transaction is closed")
if self._committed:
raise TidesDBError("Transaction already committed")

key_buf = (c_uint8 * len(key)).from_buffer_copy(key) if key else None

result = _lib.tidesdb_txn_single_delete(self._txn, cf._cf, key_buf, len(key))

if result != TDB_SUCCESS:
raise TidesDBError.from_code(result, "failed to single-delete key")

def commit(self) -> None:
"""Commit the transaction."""
if self._closed:
Expand Down
43 changes: 41 additions & 2 deletions tests/test_tidesdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,47 @@ def test_isolation_level(self, db, cf):
txn.commit()
txn.close()

def test_single_delete(self, db, cf):
"""Test single_delete behaves like delete for read semantics."""
with db.begin_txn() as txn:
txn.put(cf, b"sd_key", b"sd_value")
txn.commit()

with db.begin_txn() as txn:
txn.single_delete(cf, b"sd_key")
txn.commit()

with db.begin_txn() as txn:
with pytest.raises(tidesdb.TidesDBError):
txn.get(cf, b"sd_key")

def test_single_delete_combined_with_put(self, db, cf):
"""Test single_delete combined with other ops in the same transaction."""
with db.begin_txn() as txn:
txn.put(cf, b"sd_a", b"value_a")
txn.put(cf, b"sd_b", b"value_b")
txn.commit()

with db.begin_txn() as txn:
txn.put(cf, b"sd_c", b"value_c")
txn.single_delete(cf, b"sd_a")
txn.commit()

with db.begin_txn() as txn:
assert txn.get(cf, b"sd_b") == b"value_b"
assert txn.get(cf, b"sd_c") == b"value_c"
with pytest.raises(tidesdb.TidesDBError):
txn.get(cf, b"sd_a")

def test_single_delete_after_commit_raises(self, db, cf):
"""Test that single_delete on a committed transaction raises."""
txn = db.begin_txn()
txn.put(cf, b"sd_x", b"value_x")
txn.commit()
with pytest.raises(tidesdb.TidesDBError):
txn.single_delete(cf, b"sd_x")
txn.close()


class TestSavepoints:
"""Tests for savepoint operations."""
Expand Down Expand Up @@ -1201,14 +1242,12 @@ class TestObjectStoreConfigFields:
def test_cf_config_defaults(self):
"""Test object store config defaults in ColumnFamilyConfig."""
config = tidesdb.ColumnFamilyConfig()
assert config.object_target_file_size == 0
assert config.object_lazy_compaction is False
assert config.object_prefetch_compaction is True

def test_cf_config_custom_values(self, db):
"""Test creating column family with object store config fields set."""
config = tidesdb.ColumnFamilyConfig(
object_target_file_size=128 * 1024 * 1024,
object_lazy_compaction=True,
object_prefetch_compaction=False,
)
Expand Down
Loading