ThetaDB 0.0.2

ThetaDB 0.0.2

Tangent 维护。



ThetaDB 0.0.2

  • Tangent

ThetaDB

API Crates.io Cocoapods Version SPM License

适用于移动客户端(例如 iOS、Android)的轻量级嵌入式键值数据库,用 Rust 编写。

⚠️仍在开发和测试中,尚未准备好用于生产环境。

概览

ThetaDB适用于对移动客户端有“高读、低写”要求的场景,它使用 B+ 树作为索引管理的基础层。

受 Go 的 BoltDB 启发,ThetaDB 使用 mmap,依托操作系统保持内存和数据库文件的同步。ThetaDB 还实现了 shadow paging 来确保事务的原子性和持久性,防止数据丢失或数据库内部结构损坏。

架构

安装

ThetaDB 为这些语言提供了 API:RustSwiftKotlin

Rust - Cargo

Thetadb 添加到您的 Cargo.toml 依赖项中。

[dependencies]
thetadb = 0.0.1

Swift - 包管理器

在您的 Package.swift 中添加 ThetaDB 依赖项

.package(url: "https://github.com/TangentW/ThetaDB.git", from: "0.0.1")

然后指定 ThetaDB 作为您希望在 Target 中使用 ThetaDB 的依赖项

Swift - CocoaPods

将该库添加到您的 Podfile

pod 'ThetaDB'

然后执行

$ pod install

Java

🚧 尚待开发。

使用方法

ThetaDB 的 API 在不同语言中通常相似。

打开数据库

使用以下方法在指定路径打开数据库。如果数据库文件不存在,ThetaDB 将自动创建并初始化它。

Rust 示例
use thetadb::{Options, ThetaDB};

let path = "path/to/db.theta";

// The simplest way to open with default `Options`:
let db = ThetaDB::open(path)?;

// Open with `Options`:
let db = Options::new()
    .force_sync(true)
    .mempool_capacity(8)
    .open(path)?;
Swift 示例
import ThetaDB

let path = "path/to/db.theta"

// The simplest way to open with default `Options`:
let db = try ThetaDB(path: path)

// Open with `Options`:
let db = try ThetaDB(
    path: path,
    options: .init(
        forceSync: true,
        mempoolCapacity: 8
    )
)

ThetaDB 对文件名和扩展名没有特定要求,但建议使用 theta 作为扩展名,以便于识别。

ThetaDB 在数据库实例被销毁时将自动关闭。

获取、插入、更新、删除

Rust 示例
// Insert a new key-value pair into database.
db.put(b"foo", b"foo")?;

// Check if the database contains a given key.
assert!(db.contains(b"foo")?);
assert!(!db.contains(b"unknown")?);

// Get the value associated with a given key.
assert_eq!(
    db.get(b"foo")?,
    Some(b"foo".to_vec())
);
assert_eq!(
    db.get(b"unknown")?,
    None
);

// Update an existing value associated with a given key.
db.put(b"foo", b"bar")?;
assert_eq!(
    db.get(b"foo")?,
    Some(b"bar".to_vec())
);

// Delete an existing key-value pair from database.
db.delete(b"foo")?;
assert!(!db.contains(b"foo")?);
assert_eq!(
    db.get(b"foo")?,
    None
);
Swift 示例
// Insert a new key-value pair into database.
try db.put("foo".data(using: .utf8)!, for: "foo")

// Check if the database contains a given key.
assert(try db.contains("foo"))
assert(try !db.contains("unknown"))

// Get the value associated with a given key.
assertEq(
    try db.get("foo"),
    "foo".data(using: .utf8)
)
assertEq(
    try db.get("unknown"),
    nil
)

// Update an existing value associated with a given key.
try db.put("bar".data(using: .utf8)!, for: "foo")
assertEq(
    try db.get("foo"),
    "bar".data(using: .utf8)
)

// Delete an existing key-value pair from database.
try db.delete("foo")
assert(try !db.contains("foo"))
assertEq(
    try db.get("foo"),
    nil
)

事务

ThetaDB 有两种类型的事务:只读事务读写事务。只读事务仅允许进行只读操作,而读写事务允许修改。

ThetaDB 允许多个只读事务同时进行,但任何时刻最多只能有一个读写事务。当读写事务处于提交状态时,它将拥有对数据库的独占访问权,直到提交完成,在此期间其他尝试访问数据库的事务将会被阻塞。你可以将这种情况视为读写锁的共享访问独占访问

只读事务

Rust 示例
// Start a read-only transaction.
let tx = db.begin_tx()?;

// Then perform read-only access.
_ = tx.contains(b"foo")?;
_ = tx.get(b"foo")?;

// Or you can perform a read-only transaction using closure,
// with `view` method:
db.view(|tx| {
    _ = tx.contains(b"foo")?;
    _ = tx.get(b"foo")?;
    Ok(())
})?;
Swift 示例
// Start a read-only transaction.
let tx = try db.beginTx()

// Then perform read-only access.
_ = try tx.contains("foo")
_ = try tx.get("foo")

// Or you can perform a read-only transaction using closure,
// with `view` method:
try db.view {
    _ = try $0.contains("foo")
    _ = try $0.get("foo")
}

读写事务

ThetaDB 的读写事务被设计为自动回滚,因此除非你显式调用 commit 方法,否则事务中所做的任何更改都将被丢弃。

或者,您可以使用闭包执行读写事务,如果没有发生错误,则在闭包调用后,事务将自动提交。

Rust 示例
// Start a read-write transaction.
let mut tx = db.begin_tx_mut()?;

// Then perform read-write access.
tx.put(b"hello", b"world")?;
_ = tx.get(b"hello")?;

// Finally, commit the transaction.
tx.commit()?;

// Or you can perform a read-write transaction using closure,
// with `update` method:
db.update(|tx| {
    tx.put(b"hello", b"world")?;
    _ = tx.get(b"hello")?;
    Ok(())
})?;
Swift 示例
// Start a read-write transaction.
let tx = try db.beginTxMut()

// Then perform read-write access.
try tx.put("world".data(using: .utf8)!, for: "hello")
_ = try tx.get("hello")

// Finally, commit the transaction.
try tx.commit()

// Or you can perform a read-write transaction using closure,
// with `update` method:
try db.update {
    try $0.put("world".data(using: .utf8)!, for: "hello")
    _ = try $0.get("hello")
}

请注意

❗️ 事务实例是不可发送的,这意味着将它们发送到另一个线程是不安全的。Rust 通过利用 Ownership 系统,以及 SendSync 特性来自动执行这些要求,而 Swift 则要求我们手动确保这些保证。

❗️ 仅仅读事务和读写事务不得重叠,否则会发生死锁。

😺 因此 ThetaDB 建议如果您想使用事务,请使用带有闭包参数的 API(即 viewupdate)。

游标

我们可以使用 Cursor 自由地遍历 ThetaDB 中的数据。

例如,我们可以迭代 ThetaDB 中的所有键值对,如下所示

Rust 示例
// Forward traversal.
let mut cursor = db.first_cursor()?;
while let Some((key, value)) = cursor.key_value()? {
    println!("{:?} => {:?}", key, value);
    cursor.next()?;
}

// Backward traversal.
let mut cursor = db.last_cursor()?;
while let Some((key, value)) = cursor.key_value()? {
    println!("{:?} => {:?}", key, value);
    cursor.prev()?;
}
Swift 示例
// Forward traversal.
let cursor = try db.firstCursor()
while let (key, value) = try cursor.keyValue() {
    print("\(key) => \(value)")
    try cursor.next()
}

// Backward traversal.
let cursor2 = try db.lastCursor()
while let (key, value) = try cursor2.keyValue() {
    print("\(key) => \(value)")
    try cursor2.previous()
}

或者我们可以这样对 ThetaDB 执行范围查询

Rust 示例
let mut cursor = db.cursor_from_key(b"C")?;
// Enable `let_chains` feature, should add `#![feature(let_chains)]`
// to the crate attributes.
while let Some((key, value)) = cursor.key_value()? && key != b"G" {
    println!("{:?} => {:?}", key, value);
    cursor.next()?;
}
Swift 示例
let cursor = try db.cursor(key: "C")
while let (key, value) = try cursor.keyValue(), key != "G" {
    print("\(key) => \(value)")
    try cursor.next()
}

请注意

❗️ 游标也是一种事务(可以理解为一种只读事务),因此它也遵循上述提到的事务注意事项。

基准测试

🚧 待测量。

许可

ThetaDB 在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。