Skip to content

Commit 62a223d

Browse files
committed
feat(checkpoint): add storage checkpoint API
1 parent f77e71e commit 62a223d

File tree

9 files changed

+470
-8
lines changed

9 files changed

+470
-8
lines changed

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
[package]
44
name = "kite_sql"
5-
version = "0.2.0"
5+
version = "0.2.1"
66
edition = "2021"
7+
build = "build.rs"
78
authors = ["Kould <kould2333@gmail.com>", "Xwg <loloxwg@gmail.com>"]
89
description = "SQL as a Function for Rust"
910
documentation = "https://docs.rs/kite_sql/latest/kite_sql/"
@@ -28,6 +29,7 @@ default = ["macros", "rocksdb"]
2829
macros = []
2930
orm = []
3031
rocksdb = ["dep:rocksdb"]
32+
unsafe_txdb_checkpoint = ["rocksdb", "dep:librocksdb-sys"]
3133
lmdb = ["dep:lmdb", "dep:lmdb-sys"]
3234
net = ["rocksdb", "dep:pgwire", "dep:async-trait", "dep:clap", "dep:env_logger", "dep:futures", "dep:log", "dep:tokio"]
3335
pprof = ["pprof/criterion", "pprof/flamegraph"]
@@ -57,7 +59,7 @@ recursive = { version = "0.1" }
5759
regex = { version = "1" }
5860
rust_decimal = { version = "1" }
5961
serde = { version = "1", features = ["derive", "rc"] }
60-
kite_sql_serde_macros = { version = "0.2.0", path = "kite_sql_serde_macros" }
62+
kite_sql_serde_macros = { version = "0.2.1", path = "kite_sql_serde_macros" }
6163
siphasher = { version = "1", features = ["serde"] }
6264
sqlparser = { version = "0.61", features = ["serde"] }
6365
thiserror = { version = "1" }
@@ -86,6 +88,7 @@ sqlite = { version = "0.34" }
8688

8789
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
8890
rocksdb = { version = "0.23", optional = true }
91+
librocksdb-sys = { version = "0.17.1", optional = true }
8992
lmdb = { version = "0.8.0", optional = true }
9093
lmdb-sys = { version = "0.8.0", optional = true }
9194

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,15 @@ fn main() -> Result<(), DatabaseError> {
134134
- `build_lmdb()` opens a persistent LMDB-backed database.
135135
- `build_in_memory()` opens an in-memory database for tests, examples, and temporary workloads.
136136
- `build_optimistic()` is available on native targets when you specifically want optimistic transactions on top of RocksDB.
137+
- `Database::checkpoint(path)` creates a local consistent snapshot when the selected storage backend supports it.
137138
- Cargo features:
138139
- `rocksdb` is enabled by default
139140
- `lmdb` is optional
141+
- `unsafe_txdb_checkpoint` enables experimental checkpoint support for RocksDB `TransactionDB`
140142
- `cargo check --no-default-features --features lmdb` builds an LMDB-only native configuration
141143

142144
On native targets, `LMDB` shines when reads dominate, while `RocksDB` is usually the stronger choice when writes do.
145+
Checkpoint support and feature-gating details are documented in [docs/features.md](docs/features.md).
143146

144147
👉**more examples**
145148
- [hello_world](examples/hello_world.rs)

build.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use std::env;
2+
use std::fs;
3+
use std::path::Path;
4+
5+
const SUPPORTED_ROCKSDB_VERSION: &str = "0.23.0";
6+
const SUPPORTED_LIBROCKSDB_SYS_VERSION: &str = "0.17.1+9.9.3";
7+
8+
fn main() {
9+
println!("cargo:rerun-if-changed=Cargo.lock");
10+
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_UNSAFE_TXDB_CHECKPOINT");
11+
12+
if env::var_os("CARGO_FEATURE_UNSAFE_TXDB_CHECKPOINT").is_none() {
13+
return;
14+
}
15+
16+
let lock_path = Path::new("Cargo.lock");
17+
let lock_contents = fs::read_to_string(lock_path)
18+
.unwrap_or_else(|err| panic!("failed to read {}: {err}", lock_path.display()));
19+
20+
ensure_locked_version(
21+
&lock_contents,
22+
"rocksdb",
23+
SUPPORTED_ROCKSDB_VERSION,
24+
"unsafe_txdb_checkpoint",
25+
);
26+
ensure_locked_version(
27+
&lock_contents,
28+
"librocksdb-sys",
29+
SUPPORTED_LIBROCKSDB_SYS_VERSION,
30+
"unsafe_txdb_checkpoint",
31+
);
32+
}
33+
34+
fn ensure_locked_version(
35+
lock_contents: &str,
36+
expected_name: &str,
37+
expected_version: &str,
38+
feature_name: &str,
39+
) {
40+
let found_version = find_locked_version(lock_contents, expected_name);
41+
42+
match found_version.as_deref() {
43+
Some(version) if version == expected_version => {}
44+
Some(version) => panic!(
45+
"feature `{feature_name}` only supports `{expected_name} = {expected_version}`, found `{expected_name} = {version}` in Cargo.lock; disable the feature or re-validate the implementation"
46+
),
47+
None => panic!(
48+
"feature `{feature_name}` requires `{expected_name} = {expected_version}` in Cargo.lock, but `{expected_name}` was not found"
49+
),
50+
}
51+
}
52+
53+
fn find_locked_version(lock_contents: &str, expected_name: &str) -> Option<String> {
54+
let mut current_name = None;
55+
let mut current_version = None;
56+
57+
for line in lock_contents.lines() {
58+
let line = line.trim();
59+
if line == "[[package]]" {
60+
if current_name.as_deref() == Some(expected_name) {
61+
return current_version;
62+
}
63+
current_name = None;
64+
current_version = None;
65+
continue;
66+
}
67+
68+
if let Some(value) = extract_toml_string(line, "name") {
69+
current_name = Some(value.to_string());
70+
continue;
71+
}
72+
73+
if let Some(value) = extract_toml_string(line, "version") {
74+
current_version = Some(value.to_string());
75+
}
76+
}
77+
78+
if current_name.as_deref() == Some(expected_name) {
79+
current_version
80+
} else {
81+
None
82+
}
83+
}
84+
85+
fn extract_toml_string<'a>(line: &'a str, key: &str) -> Option<&'a str> {
86+
let prefix = match key {
87+
"name" => "name = \"",
88+
"version" => "version = \"",
89+
_ => return None,
90+
};
91+
let rest = line.strip_prefix(prefix)?;
92+
rest.strip_suffix('"')
93+
}

docs/features.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,35 @@ let kite_sql = DataBaseBuilder::path("./data")
5858
- Pessimistic (Default)
5959
- Optimistic
6060

61+
### Checkpoint
62+
KiteSQL exposes checkpoint as a storage capability rather than a full backup workflow. A checkpoint only creates a consistent local snapshot directory; compressing, uploading, retaining, and pruning backups should stay in application code.
63+
64+
Support matrix:
65+
- `build_optimistic()` supports `Database::checkpoint(...)` through RocksDB's safe checkpoint API.
66+
- `build_rocksdb()` requires Cargo feature `unsafe_txdb_checkpoint` because upstream `rocksdb` does not currently expose a safe `TransactionDB` checkpoint API.
67+
- `build_lmdb()` and `build_in_memory()` do not currently expose checkpoint support.
68+
69+
Opt in for `TransactionDB` checkpoint support:
70+
```bash
71+
cargo check --features unsafe_txdb_checkpoint
72+
```
73+
74+
Minimal usage:
75+
```rust
76+
use kite_sql::db::DataBaseBuilder;
77+
use kite_sql::errors::DatabaseError;
78+
79+
fn main() -> Result<(), DatabaseError> {
80+
let database = DataBaseBuilder::path("./data").build_rocksdb()?;
81+
82+
database.checkpoint("./backup/checkpoint-2026-03-29")?;
83+
84+
Ok(())
85+
}
86+
```
87+
88+
If `unsafe_txdb_checkpoint` is not enabled, `build_rocksdb()` returns an explicit error instead of attempting the experimental implementation.
89+
6190
### Field options
6291
- [not] null
6392
- unique

kite_sql_serde_macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "kite_sql_serde_macros"
3-
version = "0.2.0"
3+
version = "0.2.1"
44
edition = "2021"
55
description = "Derive macros for KiteSQL"
66
documentation = "https://docs.rs/kite_sql_serde_macros/latest/kite_sql_serde_macros/"

0 commit comments

Comments
 (0)