From 62a223de3e368bd3aa7821fa67b7000177b99548 Mon Sep 17 00:00:00 2001 From: kould Date: Sun, 29 Mar 2026 20:05:53 +0800 Subject: [PATCH 1/2] feat(checkpoint): add storage checkpoint API --- Cargo.lock | 5 +- Cargo.toml | 7 +- README.md | 3 + build.rs | 93 +++++++++++++++++ docs/features.md | 29 ++++++ kite_sql_serde_macros/Cargo.toml | 2 +- src/db.rs | 174 ++++++++++++++++++++++++++++++- src/storage/mod.rs | 7 ++ src/storage/rocksdb.rs | 158 +++++++++++++++++++++++++++- 9 files changed, 470 insertions(+), 8 deletions(-) create mode 100644 build.rs diff --git a/Cargo.lock b/Cargo.lock index ce235dbf..b8bbf43f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1307,7 +1307,7 @@ dependencies = [ [[package]] name = "kite_sql" -version = "0.2.0" +version = "0.2.1" dependencies = [ "ahash 0.8.12", "async-trait", @@ -1329,6 +1329,7 @@ dependencies = [ "itertools 0.12.1", "js-sys", "kite_sql_serde_macros", + "librocksdb-sys", "lmdb", "lmdb-sys", "log", @@ -1360,7 +1361,7 @@ dependencies = [ [[package]] name = "kite_sql_serde_macros" -version = "0.2.0" +version = "0.2.1" dependencies = [ "darling", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 0677305a..e8cf507d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,9 @@ [package] name = "kite_sql" -version = "0.2.0" +version = "0.2.1" edition = "2021" +build = "build.rs" authors = ["Kould ", "Xwg "] description = "SQL as a Function for Rust" documentation = "https://docs.rs/kite_sql/latest/kite_sql/" @@ -28,6 +29,7 @@ default = ["macros", "rocksdb"] macros = [] orm = [] rocksdb = ["dep:rocksdb"] +unsafe_txdb_checkpoint = ["rocksdb", "dep:librocksdb-sys"] lmdb = ["dep:lmdb", "dep:lmdb-sys"] net = ["rocksdb", "dep:pgwire", "dep:async-trait", "dep:clap", "dep:env_logger", "dep:futures", "dep:log", "dep:tokio"] pprof = ["pprof/criterion", "pprof/flamegraph"] @@ -57,7 +59,7 @@ recursive = { version = "0.1" } regex = { version = "1" } rust_decimal = { version = "1" } serde = { version = "1", features = ["derive", "rc"] } -kite_sql_serde_macros = { version = "0.2.0", path = "kite_sql_serde_macros" } +kite_sql_serde_macros = { version = "0.2.1", path = "kite_sql_serde_macros" } siphasher = { version = "1", features = ["serde"] } sqlparser = { version = "0.61", features = ["serde"] } thiserror = { version = "1" } @@ -86,6 +88,7 @@ sqlite = { version = "0.34" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rocksdb = { version = "0.23", optional = true } +librocksdb-sys = { version = "0.17.1", optional = true } lmdb = { version = "0.8.0", optional = true } lmdb-sys = { version = "0.8.0", optional = true } diff --git a/README.md b/README.md index ff6898bf..36932aaf 100755 --- a/README.md +++ b/README.md @@ -134,12 +134,15 @@ fn main() -> Result<(), DatabaseError> { - `build_lmdb()` opens a persistent LMDB-backed database. - `build_in_memory()` opens an in-memory database for tests, examples, and temporary workloads. - `build_optimistic()` is available on native targets when you specifically want optimistic transactions on top of RocksDB. +- `Database::checkpoint(path)` creates a local consistent snapshot when the selected storage backend supports it. - Cargo features: - `rocksdb` is enabled by default - `lmdb` is optional + - `unsafe_txdb_checkpoint` enables experimental checkpoint support for RocksDB `TransactionDB` - `cargo check --no-default-features --features lmdb` builds an LMDB-only native configuration On native targets, `LMDB` shines when reads dominate, while `RocksDB` is usually the stronger choice when writes do. +Checkpoint support and feature-gating details are documented in [docs/features.md](docs/features.md). 👉**more examples** - [hello_world](examples/hello_world.rs) diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..f43eae1e --- /dev/null +++ b/build.rs @@ -0,0 +1,93 @@ +use std::env; +use std::fs; +use std::path::Path; + +const SUPPORTED_ROCKSDB_VERSION: &str = "0.23.0"; +const SUPPORTED_LIBROCKSDB_SYS_VERSION: &str = "0.17.1+9.9.3"; + +fn main() { + println!("cargo:rerun-if-changed=Cargo.lock"); + println!("cargo:rerun-if-env-changed=CARGO_FEATURE_UNSAFE_TXDB_CHECKPOINT"); + + if env::var_os("CARGO_FEATURE_UNSAFE_TXDB_CHECKPOINT").is_none() { + return; + } + + let lock_path = Path::new("Cargo.lock"); + let lock_contents = fs::read_to_string(lock_path) + .unwrap_or_else(|err| panic!("failed to read {}: {err}", lock_path.display())); + + ensure_locked_version( + &lock_contents, + "rocksdb", + SUPPORTED_ROCKSDB_VERSION, + "unsafe_txdb_checkpoint", + ); + ensure_locked_version( + &lock_contents, + "librocksdb-sys", + SUPPORTED_LIBROCKSDB_SYS_VERSION, + "unsafe_txdb_checkpoint", + ); +} + +fn ensure_locked_version( + lock_contents: &str, + expected_name: &str, + expected_version: &str, + feature_name: &str, +) { + let found_version = find_locked_version(lock_contents, expected_name); + + match found_version.as_deref() { + Some(version) if version == expected_version => {} + Some(version) => panic!( + "feature `{feature_name}` only supports `{expected_name} = {expected_version}`, found `{expected_name} = {version}` in Cargo.lock; disable the feature or re-validate the implementation" + ), + None => panic!( + "feature `{feature_name}` requires `{expected_name} = {expected_version}` in Cargo.lock, but `{expected_name}` was not found" + ), + } +} + +fn find_locked_version(lock_contents: &str, expected_name: &str) -> Option { + let mut current_name = None; + let mut current_version = None; + + for line in lock_contents.lines() { + let line = line.trim(); + if line == "[[package]]" { + if current_name.as_deref() == Some(expected_name) { + return current_version; + } + current_name = None; + current_version = None; + continue; + } + + if let Some(value) = extract_toml_string(line, "name") { + current_name = Some(value.to_string()); + continue; + } + + if let Some(value) = extract_toml_string(line, "version") { + current_version = Some(value.to_string()); + } + } + + if current_name.as_deref() == Some(expected_name) { + current_version + } else { + None + } +} + +fn extract_toml_string<'a>(line: &'a str, key: &str) -> Option<&'a str> { + let prefix = match key { + "name" => "name = \"", + "version" => "version = \"", + _ => return None, + }; + let rest = line.strip_prefix(prefix)?; + rest.strip_suffix('"') +} diff --git a/docs/features.md b/docs/features.md index ca672aa6..2317733a 100644 --- a/docs/features.md +++ b/docs/features.md @@ -58,6 +58,35 @@ let kite_sql = DataBaseBuilder::path("./data") - Pessimistic (Default) - Optimistic +### Checkpoint +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. + +Support matrix: +- `build_optimistic()` supports `Database::checkpoint(...)` through RocksDB's safe checkpoint API. +- `build_rocksdb()` requires Cargo feature `unsafe_txdb_checkpoint` because upstream `rocksdb` does not currently expose a safe `TransactionDB` checkpoint API. +- `build_lmdb()` and `build_in_memory()` do not currently expose checkpoint support. + +Opt in for `TransactionDB` checkpoint support: +```bash +cargo check --features unsafe_txdb_checkpoint +``` + +Minimal usage: +```rust +use kite_sql::db::DataBaseBuilder; +use kite_sql::errors::DatabaseError; + +fn main() -> Result<(), DatabaseError> { + let database = DataBaseBuilder::path("./data").build_rocksdb()?; + + database.checkpoint("./backup/checkpoint-2026-03-29")?; + + Ok(()) +} +``` + +If `unsafe_txdb_checkpoint` is not enabled, `build_rocksdb()` returns an explicit error instead of attempting the experimental implementation. + ### Field options - [not] null - unique diff --git a/kite_sql_serde_macros/Cargo.toml b/kite_sql_serde_macros/Cargo.toml index e590741e..c5ac4788 100644 --- a/kite_sql_serde_macros/Cargo.toml +++ b/kite_sql_serde_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kite_sql_serde_macros" -version = "0.2.0" +version = "0.2.1" edition = "2021" description = "Derive macros for KiteSQL" documentation = "https://docs.rs/kite_sql_serde_macros/latest/kite_sql_serde_macros/" diff --git a/src/db.rs b/src/db.rs index ccd26380..8ca92275 100644 --- a/src/db.rs +++ b/src/db.rs @@ -37,7 +37,9 @@ use crate::storage::lmdb::{LmdbConfig, LmdbStorage}; use crate::storage::memory::MemoryStorage; #[cfg(all(not(target_arch = "wasm32"), feature = "rocksdb"))] use crate::storage::rocksdb::{OptimisticRocksStorage, RocksStorage, StorageConfig}; -use crate::storage::{StatisticsMetaCache, Storage, TableCache, Transaction, ViewCache}; +use crate::storage::{ + CheckpointableStorage, StatisticsMetaCache, Storage, TableCache, Transaction, ViewCache, +}; use crate::types::tuple::{SchemaRef, Tuple}; use crate::types::value::DataValue; use crate::utils::lru::SharedLruCache; @@ -47,6 +49,7 @@ use parking_lot::{RawRwLock, RwLock}; use std::hash::RandomState; use std::marker::PhantomData; use std::mem; +use std::path::Path; use std::path::PathBuf; use std::sync::atomic::AtomicUsize; use std::sync::Arc; @@ -675,6 +678,19 @@ impl Database { } } +impl Database +where + S: CheckpointableStorage, +{ + /// Creates an online consistent checkpoint in `path`. + /// + /// The target path must not exist or must be an empty directory. + #[inline] + pub fn checkpoint>(&self, path: P) -> Result<(), DatabaseError> { + self.storage.create_checkpoint(path) + } +} + /// Borrowing interface for result iterators returned by database execution APIs. pub trait BorrowResultIter { /// Returns the output schema for the current result set. @@ -1003,8 +1019,15 @@ pub(crate) mod test { use crate::types::value::DataValue; use crate::types::LogicalType; use chrono::{Datelike, Local}; + use std::io::ErrorKind; use std::sync::atomic::AtomicUsize; + #[cfg(feature = "unsafe_txdb_checkpoint")] + use std::sync::atomic::Ordering; use std::sync::Arc; + #[cfg(feature = "unsafe_txdb_checkpoint")] + use std::thread; + #[cfg(feature = "unsafe_txdb_checkpoint")] + use std::time::Duration; use tempfile::TempDir; pub(crate) fn build_table( @@ -1033,6 +1056,23 @@ pub(crate) mod test { Ok(()) } + #[cfg(feature = "unsafe_txdb_checkpoint")] + fn query_i32( + database: &crate::db::Database, + sql: &str, + ) -> Result { + let mut iter = database.run(sql)?; + let value = match iter.next().transpose()?.map(|tuple| tuple.values) { + Some(values) => match values.as_slice() { + [DataValue::Int32(value)] => *value, + other => panic!("expected a single Int32 column, got {other:?}"), + }, + None => panic!("expected one result row for query: {sql}"), + }; + iter.done()?; + Ok(value) + } + #[test] fn test_run_sql() -> Result<(), DatabaseError> { let temp_dir = TempDir::new().expect("unable to create temporary working directory"); @@ -1780,4 +1820,136 @@ pub(crate) mod test { Err(DatabaseError::InvalidValue(message)) if message == "histogram buckets must be >= 1" )); } + + #[cfg(feature = "unsafe_txdb_checkpoint")] + #[test] + fn test_checkpoint_restores_snapshot() -> Result<(), DatabaseError> { + let temp_dir = TempDir::new().expect("unable to create temporary working directory"); + let live_path = temp_dir.path().join("live"); + let checkpoint_path = temp_dir.path().join("checkpoint"); + let kite_sql = DataBaseBuilder::path(&live_path).build_rocksdb()?; + + kite_sql + .run("create table t_checkpoint (id int primary key, v int)")? + .done()?; + kite_sql + .run("insert into t_checkpoint values (1, 10), (2, 20)")? + .done()?; + + kite_sql.checkpoint(&checkpoint_path)?; + + kite_sql + .run("insert into t_checkpoint values (3, 30)")? + .done()?; + + let snapshot = DataBaseBuilder::path(&checkpoint_path).build_rocksdb()?; + assert_eq!( + query_i32(&snapshot, "select count(*) from t_checkpoint")?, + 2 + ); + assert_eq!( + query_i32(&kite_sql, "select count(*) from t_checkpoint")?, + 3 + ); + + Ok(()) + } + + #[test] + fn test_checkpoint_rejects_non_empty_target_dir() -> Result<(), DatabaseError> { + let temp_dir = TempDir::new().expect("unable to create temporary working directory"); + let live_path = temp_dir.path().join("live"); + let checkpoint_path = temp_dir.path().join("checkpoint"); + let kite_sql = DataBaseBuilder::path(&live_path).build_rocksdb()?; + + std::fs::create_dir(&checkpoint_path)?; + std::fs::write(checkpoint_path.join("stale.txt"), b"stale")?; + + let err = kite_sql + .checkpoint(&checkpoint_path) + .expect_err("checkpoint should reject non-empty directories"); + assert!(matches!( + err, + DatabaseError::IO(ref io_err) if io_err.kind() == ErrorKind::AlreadyExists + )); + + Ok(()) + } + + #[cfg(not(feature = "unsafe_txdb_checkpoint"))] + #[test] + fn test_checkpoint_requires_unsafe_feature() -> Result<(), DatabaseError> { + let temp_dir = TempDir::new().expect("unable to create temporary working directory"); + let live_path = temp_dir.path().join("live"); + let checkpoint_path = temp_dir.path().join("checkpoint"); + let kite_sql = DataBaseBuilder::path(&live_path).build_rocksdb()?; + + kite_sql + .run("create table t_checkpoint_disabled (id int primary key, v int)")? + .done()?; + + let err = kite_sql + .checkpoint(&checkpoint_path) + .expect_err("checkpoint should require the unsafe feature"); + assert!(matches!(err, DatabaseError::UnsupportedStmt(_))); + + Ok(()) + } + + #[cfg(feature = "unsafe_txdb_checkpoint")] + #[test] + fn test_checkpoint_during_concurrent_writes() -> Result<(), DatabaseError> { + let temp_dir = TempDir::new().expect("unable to create temporary working directory"); + let live_path = temp_dir.path().join("live"); + let checkpoint_path = temp_dir.path().join("checkpoint"); + let kite_sql = Arc::new(DataBaseBuilder::path(&live_path).build_rocksdb()?); + + kite_sql + .run("create table t_checkpoint_concurrent (id int primary key, v int)")? + .done()?; + + let inserted = Arc::new(AtomicUsize::new(0)); + let writer_db = Arc::clone(&kite_sql); + let writer_inserted = Arc::clone(&inserted); + let writer = thread::spawn(move || -> Result { + for i in 0..64 { + writer_db + .run(format!( + "insert into t_checkpoint_concurrent values ({i}, {i})" + ))? + .done()?; + writer_inserted.store(i + 1, Ordering::SeqCst); + + if i >= 8 { + thread::sleep(Duration::from_millis(2)); + } + } + + Ok(64) + }); + + while inserted.load(Ordering::SeqCst) < 8 { + thread::yield_now(); + } + + kite_sql.checkpoint(&checkpoint_path)?; + + let total = writer.join().expect("writer thread should not panic")?; + let snapshot = DataBaseBuilder::path(&checkpoint_path).build_rocksdb()?; + let snapshot_count = query_i32(&snapshot, "select count(*) from t_checkpoint_concurrent")?; + let consistent_count = query_i32( + &snapshot, + "select count(*) from t_checkpoint_concurrent where id = v", + )?; + + assert!(snapshot_count >= 8); + assert!(snapshot_count <= total as i32); + assert_eq!(snapshot_count, consistent_count); + assert_eq!( + query_i32(&kite_sql, "select count(*) from t_checkpoint_concurrent")?, + total as i32 + ); + + Ok(()) + } } diff --git a/src/storage/mod.rs b/src/storage/mod.rs index c910bbfe..8850e390 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -45,6 +45,7 @@ use std::fmt::{self, Display, Formatter}; use std::io::Cursor; use std::mem; use std::ops::SubAssign; +use std::path::Path; use std::sync::Arc; pub type KeyValueRef<'a> = (&'a [u8], &'a [u8]); @@ -99,6 +100,12 @@ pub trait Storage: Clone { } } +/// Optional capability for storage engines that can materialize an online +/// consistent checkpoint to the local filesystem. +pub trait CheckpointableStorage: Storage { + fn create_checkpoint>(&self, path: P) -> Result<(), DatabaseError>; +} + /// Optional bounds of the reader, of the form (offset, limit). pub(crate) type Bounds = (Option, Option); diff --git a/src/storage/rocksdb.rs b/src/storage/rocksdb.rs index 4d062db6..9d5f9fb4 100644 --- a/src/storage/rocksdb.rs +++ b/src/storage/rocksdb.rs @@ -14,21 +14,30 @@ use crate::errors::DatabaseError; use crate::storage::table_codec::{Bytes, TableCodec}; -use crate::storage::{InnerIter, Storage, Transaction}; +use crate::storage::{CheckpointableStorage, InnerIter, Storage, Transaction}; +#[cfg(feature = "unsafe_txdb_checkpoint")] +use librocksdb_sys as ffi; use rocksdb::{ + checkpoint::Checkpoint, statistics::{StatsLevel, Ticker}, DBPinnableSlice, DBRawIteratorWithThreadMode, OptimisticTransactionDB, Options, ReadOptions, SliceTransform, TransactionDB, }; use std::collections::Bound; +#[cfg(feature = "unsafe_txdb_checkpoint")] +use std::ffi::{c_char, c_void, CStr, CString}; use std::fmt::{self, Display, Formatter}; -use std::path::PathBuf; +use std::fs; +use std::io::{self, ErrorKind}; +use std::path::{Path, PathBuf}; use std::sync::Arc; // Table data keys are `{table_hash(8)}{type_tag(1)}...`, so use hash+type as prefix. const ROCKSDB_FIXED_PREFIX_LEN: usize = 9; const ROCKSDB_BLOOM_BITS_PER_KEY: f64 = 10.0; const ROCKSDB_MEMTABLE_PREFIX_BLOOM_RATIO: f64 = 0.10; +#[cfg(feature = "unsafe_txdb_checkpoint")] +const ROCKSDB_TRANSACTION_DB_INNER_OFFSET: usize = 0x30; /// A lightweight snapshot of key RocksDB runtime indicators for tuning and diagnostics. #[derive(Debug, Clone, Default, PartialEq)] @@ -309,6 +318,114 @@ fn default_opts(config: StorageConfig) -> Options { opts } +fn prepare_checkpoint_dir(path: &Path) -> Result<(), DatabaseError> { + match fs::metadata(path) { + Ok(metadata) => { + if !metadata.is_dir() { + return Err(io::Error::new( + ErrorKind::AlreadyExists, + format!( + "checkpoint target path '{}' already exists and is not a directory", + path.display() + ), + ) + .into()); + } + + if fs::read_dir(path)?.next().is_some() { + return Err(io::Error::new( + ErrorKind::AlreadyExists, + format!( + "checkpoint target directory '{}' must be empty", + path.display() + ), + ) + .into()); + } + + fs::remove_dir(path)?; + Ok(()) + } + Err(err) if err.kind() == ErrorKind::NotFound => Ok(()), + Err(err) => Err(err.into()), + } +} + +fn cleanup_failed_checkpoint_dir(path: &Path) -> Result<(), DatabaseError> { + match fs::remove_dir_all(path) { + Ok(()) => Ok(()), + Err(err) if err.kind() == ErrorKind::NotFound => Ok(()), + Err(err) => Err(err.into()), + } +} + +#[cfg(not(feature = "unsafe_txdb_checkpoint"))] +fn unsupported_transactiondb_checkpoint_error() -> DatabaseError { + DatabaseError::UnsupportedStmt(format!( + "rocksdb TransactionDB checkpoint is disabled; enable the `unsafe_txdb_checkpoint` feature to opt in to the current implementation", + )) +} + +#[cfg(feature = "unsafe_txdb_checkpoint")] +fn rocksdb_error_from_ptr(err: *mut c_char) -> DatabaseError { + unsafe { + let message = CStr::from_ptr(err).to_string_lossy().into_owned(); + ffi::rocksdb_free(err.cast::()); + io::Error::other(message).into() + } +} + +#[cfg(feature = "unsafe_txdb_checkpoint")] +fn checkpoint_path_to_cstring(path: &Path) -> Result { + CString::new(path.to_string_lossy().into_owned()).map_err(|_| { + io::Error::new( + ErrorKind::InvalidInput, + format!( + "checkpoint path '{}' contains an interior NUL byte", + path.display() + ), + ) + .into() + }) +} + +#[cfg(feature = "unsafe_txdb_checkpoint")] +fn create_transactiondb_checkpoint( + db: &TransactionDB, + path: &Path, +) -> Result<(), DatabaseError> { + let path = checkpoint_path_to_cstring(path)?; + let mut err: *mut c_char = std::ptr::null_mut(); + + // `rocksdb` 0.23 does not expose a safe checkpoint API for `TransactionDB`. + // This fallback is intentionally gated behind the `unsafe_txdb_checkpoint` + // feature so callers must opt in explicitly. + let db_ptr = unsafe { + *(std::ptr::from_ref(db) + .cast::() + .add(ROCKSDB_TRANSACTION_DB_INNER_OFFSET) + .cast::<*mut ffi::rocksdb_transactiondb_t>()) + }; + let checkpoint = + unsafe { ffi::rocksdb_transactiondb_checkpoint_object_create(db_ptr, &mut err) }; + if !err.is_null() { + return Err(rocksdb_error_from_ptr(err)); + } + if checkpoint.is_null() { + return Err(io::Error::other("Could not create checkpoint object.").into()); + } + + unsafe { + ffi::rocksdb_checkpoint_create(checkpoint, path.as_ptr(), 0, &mut err); + ffi::rocksdb_checkpoint_object_destroy(checkpoint); + } + if !err.is_null() { + return Err(rocksdb_error_from_ptr(err)); + } + + Ok(()) +} + impl Storage for OptimisticRocksStorage { type Metrics = RocksDbMetrics; @@ -361,6 +478,43 @@ impl Storage for RocksStorage { } } +impl CheckpointableStorage for OptimisticRocksStorage { + fn create_checkpoint>(&self, path: P) -> Result<(), DatabaseError> { + let path = path.as_ref(); + prepare_checkpoint_dir(path)?; + + let checkpoint = Checkpoint::new(self.inner.as_ref())?; + if let Err(err) = checkpoint.create_checkpoint(path) { + cleanup_failed_checkpoint_dir(path)?; + return Err(err.into()); + } + + Ok(()) + } +} + +impl CheckpointableStorage for RocksStorage { + fn create_checkpoint>(&self, path: P) -> Result<(), DatabaseError> { + let path = path.as_ref(); + prepare_checkpoint_dir(path)?; + + #[cfg(feature = "unsafe_txdb_checkpoint")] + { + if let Err(err) = create_transactiondb_checkpoint(self.inner.as_ref(), path) { + cleanup_failed_checkpoint_dir(path)?; + return Err(err); + } + + return Ok(()); + } + + #[cfg(not(feature = "unsafe_txdb_checkpoint"))] + { + return Err(unsupported_transactiondb_checkpoint_error()); + } + } +} + pub struct OptimisticRocksTransaction<'db> { tx: rocksdb::Transaction<'db, OptimisticTransactionDB>, table_codec: TableCodec, From 87a7d2648e3ff6a6e86d6bae047aed7336dd2855 Mon Sep 17 00:00:00 2001 From: kould Date: Sun, 29 Mar 2026 20:14:11 +0800 Subject: [PATCH 2/2] chore(release): keep macros crate at 0.2.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- kite_sql_serde_macros/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8bbf43f..3d23a15b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1361,7 +1361,7 @@ dependencies = [ [[package]] name = "kite_sql_serde_macros" -version = "0.2.1" +version = "0.2.0" dependencies = [ "darling", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index e8cf507d..682946f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ recursive = { version = "0.1" } regex = { version = "1" } rust_decimal = { version = "1" } serde = { version = "1", features = ["derive", "rc"] } -kite_sql_serde_macros = { version = "0.2.1", path = "kite_sql_serde_macros" } +kite_sql_serde_macros = { version = "0.2.0", path = "kite_sql_serde_macros" } siphasher = { version = "1", features = ["serde"] } sqlparser = { version = "0.61", features = ["serde"] } thiserror = { version = "1" } diff --git a/kite_sql_serde_macros/Cargo.toml b/kite_sql_serde_macros/Cargo.toml index c5ac4788..e590741e 100644 --- a/kite_sql_serde_macros/Cargo.toml +++ b/kite_sql_serde_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kite_sql_serde_macros" -version = "0.2.1" +version = "0.2.0" edition = "2021" description = "Derive macros for KiteSQL" documentation = "https://docs.rs/kite_sql_serde_macros/latest/kite_sql_serde_macros/"