From 7ca0f961121607d4260dcf179a23e196a1deec6f Mon Sep 17 00:00:00 2001
From: Elio Neto
Date: Sat, 7 Mar 2026 21:51:50 -0300
Subject: [PATCH 01/11] Add documentation badge to README
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d2f58d1..7306905 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
# ๐๏ธ ApexStore
-
+
+[](https://elioneto.github.io/ApexStore/)
[](https://opensource.org/licenses/MIT)
[](https://www.rust-lang.org/)
[](https://github.com/ElioNeto/ApexStore/releases)
From 4661f023705deedae96d21070d7349b56d33fb81 Mon Sep 17 00:00:00 2001
From: Elio Neto
Date: Sat, 7 Mar 2026 21:56:18 -0300
Subject: [PATCH 02/11] Update README.md
---
README.md | 509 +++++++++++-------------------------------------------
1 file changed, 102 insertions(+), 407 deletions(-)
diff --git a/README.md b/README.md
index 7306905..9291da4 100644
--- a/README.md
+++ b/README.md
@@ -1,55 +1,72 @@
- # ๐๏ธ ApexStore
-
-[](https://elioneto.github.io/ApexStore/)
-[](https://opensource.org/licenses/MIT)
-[](https://www.rust-lang.org/)
-[](https://github.com/ElioNeto/ApexStore/releases)
-[](https://www.docker.com/)
+
+
+
+
+ApexStore
+
+
+ High-performance, embedded Key-Value engine built with Rust ๐ฆ
+
+ Implementing LSM-Tree architecture with a focus on SOLID principles, observability, and performance.
+
+
+
+
+
+
+
+
+
-A high-performance, embedded key-value store written in Rust, implementing the **Log-Structured Merge-Tree (LSM-Tree)** architecture. Built with SOLID principles for production-grade reliability, testability, and maintainability.
+---
## ๐ฏ Overview
-ApexStore is a modern, Rust-based storage engine designed for write-heavy workloads. It combines the durability of write-ahead logging with the efficiency of LSM-Tree architecture, providing:
+ApexStore is a modern, Rust-based storage engine designed for write-heavy workloads. It combines the durability of write-ahead logging (WAL) with the efficiency of **Log-Structured Merge-Tree (LSM-Tree)** architecture.
+
+Built from the ground up using **SOLID principles**, it provides a production-grade storage solution that is easy to reason about, test, and maintain, while delivering the performance expected from a systems-level language.
+
+## โ๏ธ Why ApexStore?
+
+While industry giants like RocksDB or LevelDB focus on extreme complexity, ApexStore offers:
+
+- **Educational Clarity**: A clean, modular implementation of LSM-Tree that serves as a blueprint for high-performance systems.
+- **Strict SOLID Compliance**: Leveraging Rust's ownership model to enforce clear boundaries between MemTable, WAL, and SSTable layers.
+- **Observability First**: Built-in real-time metrics for memory, disk usage, and WAL health.
+- **Modern Defaults**: Native LZ4 compression, Bloom Filters, and 35+ tunable parameters via environment variables.
+
+## ๐ Performance Benchmarks
+
+*Measured on AMD Ryzen 9 5900X, NVMe SSD (v1.4.0)*
-- **High Write Throughput**: Optimized for write-intensive applications with in-memory buffering and sequential disk writes
-- **Data Durability**: Write-ahead log (WAL) ensures zero data loss on crashes
-- **Efficient Storage**: Block-based compression with LZ4 reduces storage footprint by 2-4x
-- **Flexible Configuration**: 35+ tunable parameters via environment variablesโno recompilation needed
-- **Production Ready**: Comprehensive error handling, metrics, and monitoring capabilities
+| Operation | Throughput | Visual |
+|-----------|------------|--------|
+| **In-Memory Writes** | ~500k ops/s | โโโโโโโโโโโโโโโโ 100% |
+| **Writes (with WAL)** | ~100k ops/s | โโโ 20% |
+| **Batch Writes** | ~1M ops/s | โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 200% |
+| **MemTable Hits** | ~1.2M ops/s | โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 240% |
+| **SSTable Reads** | ~50k ops/s | โ 10% |
+
+> **Note:** The performance difference between In-Memory and WAL writes highlights the fsync overhead, which can be optimized via `WAL_SYNC_MODE`.
## โจ Key Features
-### Storage Engine
-- **MemTable**: In-memory BTreeMap with configurable size limits for fast writes
-- **Write-Ahead Log (WAL)**: ACID-compliant durability with configurable sync modes
-- **SSTable V2**: Block-based storage format with:
- - Sparse indexing for O(log N) lookups
- - LZ4 compression for space efficiency
- - Bloom filters to avoid unnecessary disk I/O
- - Comprehensive metadata tracking
-- **Automatic Flushing**: Seamless transition from memory to disk when thresholds are reached
-- **Crash Recovery**: Automatic WAL replay on startup
-
-### Access Patterns
-- **Interactive CLI**: REPL interface for development and debugging
-- **REST API**: Full HTTP API with JSON payloads for production use
-- **Batch Operations**: Efficient bulk inserts and updates
-- **Search Capabilities**: Prefix and substring search (with iterator improvements coming in v2.0)
-
-### Advanced Features
-- **Feature Flags System**: Dynamic runtime configuration with optimistic locking
-- **Statistics & Monitoring**: Real-time metrics for memory, disk, and WAL usage
-- **Environment-Based Config**: 35+ parameters organized by category:
- - Server HTTP (12 params): networking, threading, timeouts
- - LSM Engine (8 params): storage, caching, indexing
- - Compaction (5 params): future-ready configuration
- - Advanced Tuning (6 params): I/O, memory pools, mmap
- - Monitoring (4 params): logging, metrics, telemetry
+### ๐ ๏ธ Storage Engine
+- **MemTable**: In-memory BTreeMap with configurable size limits.
+- **Write-Ahead Log (WAL)**: ACID-compliant durability with configurable sync modes.
+- **SSTable V2**: Block-based storage with Sparse Indexing and LZ4 Compression.
+- **Bloom Filters**: Drastically reduces unnecessary disk I/O for read operations.
+- **Crash Recovery**: Automatic WAL replay on startup to ensure zero data loss.
+
+### ๐ Access Patterns
+- **Interactive CLI**: REPL interface for development and debugging.
+- **REST API**: Full HTTP API with JSON payloads for microservices.
+- **Batch Operations**: Efficient bulk inserts and updates.
+- **Search Capabilities**: Prefix and substring search (Optimized iterators coming in v2.0).
## ๐๏ธ Architecture
-The engine follows a modular SOLID architecture where each component has a single responsibility:
+The engine follows a modular architecture where each component has a single responsibility:
```mermaid
graph TB
@@ -96,426 +113,104 @@ graph TB
style SST fill:#9cf,stroke:#333,stroke-width:2px
```
-### Data Flow: Write Path
-
-```mermaid
-sequenceDiagram
- participant Client
- participant Engine
- participant WAL
- participant MemTable
- participant Builder
- participant SSTable
-
- Client->>Engine: put(key, value)
- Engine->>WAL: append(record)
- WAL-->>Engine: โ persisted
- Engine->>MemTable: insert(key, value)
-
- alt MemTable Full
- Engine->>Builder: new(config, timestamp)
- loop For each entry
- Engine->>Builder: add(key, record)
- end
- Builder->>Builder: compress blocks (LZ4)
- Builder->>SSTable: write(blocks + metadata + footer)
- Builder-->>Engine: SSTable path
- Engine->>MemTable: clear()
- Engine->>WAL: truncate()
- end
-
- Engine-->>Client: โ success
-```
-
-### Data Flow: Read Path
-
-```mermaid
-sequenceDiagram
- participant Client
- participant Engine
- participant MemTable
- participant SSTable
- participant BloomFilter
-
- Client->>Engine: get(key)
- Engine->>MemTable: lookup(key)
-
- alt Key in MemTable
- MemTable-->>Engine: value
- Engine-->>Client: value
- else Not in MemTable
- loop For each SSTable (newest first)
- Engine->>BloomFilter: might_contain(key)
- alt Bloom says "no"
- BloomFilter-->>Engine: โ skip
- else Bloom says "maybe"
- Engine->>SSTable: binary_search_blocks(key)
- alt Key found
- SSTable->>SSTable: decompress_block()
- SSTable-->>Engine: value
- Engine-->>Client: value
- end
- end
- end
- Engine-->>Client: โ not found
- end
-```
-
## ๐ Quick Start
### Prerequisites
+- **Rust 1.70+**: Install via [rustup.rs](https://rustup.rs/)
-- **Rust 1.70+**: Install via [rustup](https://rustup.rs/)
- ```bash
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- ```
-
-### Installation
-
+### Installation & Run
```bash
-# Clone the repository
-git clone https://github.com/ElioNeto/ApexStore.git
-cd ApexStore
-
-# Build the project
-cargo build --release
-```
-
-### Usage
+# Clone and enter
+git clone https://github.com/ElioNeto/ApexStore.git && cd ApexStore
-#### Interactive CLI Mode
-
-```bash
-# Start the REPL
+# Build and Start REPL
cargo run --release
# Available commands:
-# > put key value
-# > get key
-# > delete key
+# > put user:1 "John Doe"
+# > get user:1
# > stats
-# > help
-# > exit
```
-#### API Server Mode
-
-```bash
-# Copy environment template (optional)
-cp .env.example .env
-
-# Customize settings (optional)
-nano .env
-
-# Start the server
-cargo run --release --features api --bin apexstore-server
-```
-
-The server will start at `http://0.0.0.0:8080` by default.
-
## ๐ณ Docker Deployment
-### Quick Start with Docker Compose (Recommended)
+Run ApexStore as a standalone API server:
```bash
-# Clone the repository
-git clone https://github.com/ElioNeto/ApexStore.git
-cd ApexStore
-
-# Copy environment file and customize (optional)
-cp .env.example .env
-
-# Start ApexStore
+# Start with Docker Compose
docker-compose up -d
-# View logs
-docker-compose logs -f apexstore
-
-# Stop the service
-docker-compose down
-```
-
-The API will be available at `http://localhost:8080`.
-
-### Standalone Docker Commands
-
-```bash
-# Build the Docker image
-docker build -t apexstore:latest .
-
-# Run the container
-docker run -d \
- --name apexstore-server \
- -p 8080:8080 \
- -v apexstore-data:/data \
- -e MEMTABLE_MAX_SIZE=16777216 \
- -e BLOCK_CACHE_SIZE_MB=64 \
- apexstore:latest
-
-# View logs
-docker logs -f apexstore-server
-
-# Stop the container
-docker stop apexstore-server
-```
-
-### Docker Configuration
-
-You can pass environment variables to configure ApexStore:
-
-```bash
+# Manual run with custom config
docker run -d \
--name apexstore-server \
-p 8080:8080 \
- -v apexstore-data:/data \
- -e HOST=0.0.0.0 \
- -e PORT=8080 \
-e MEMTABLE_MAX_SIZE=33554432 \
- -e BLOCK_SIZE=8192 \
- -e BLOCK_CACHE_SIZE_MB=128 \
- -e BLOOM_FALSE_POSITIVE_RATE=0.005 \
- -e API_AUTH_ENABLED=true \
- apexstore:latest
-```
-
-### Health Check
-
-Docker includes automatic health checks:
-
-```bash
-# Check container health status
-docker ps
-
-# Manual health check
-curl http://localhost:8080/health
-```
-
-### Data Persistence
-
-ApexStore data is persisted in a Docker volume:
-
-```bash
-# List volumes
-docker volume ls | grep apexstore
-
-# Inspect volume
-docker volume inspect apexstore-data
-
-# Backup data
-docker run --rm -v apexstore-data:/data -v $(pwd):/backup alpine \
- tar czf /backup/apexstore-backup.tar.gz -C /data .
-
-# Restore data
-docker run --rm -v apexstore-data:/data -v $(pwd):/backup alpine \
- tar xzf /backup/apexstore-backup.tar.gz -C /data
+ -v apexstore-data:/data \
+ elioneto/apexstore:latest
```
-## ๐ REST API
-
-### Core Operations
-
-| Method | Endpoint | Description | Example |
-|--------|----------|-------------|----------|
-| `POST` | `/keys` | Insert or update a key | `{"key": "user:1", "value": "Alice"}` |
-| `GET` | `/keys/{key}` | Retrieve a value by key | `/keys/user:1` |
-| `DELETE` | `/keys/{key}` | Delete a key (tombstone) | `/keys/user:1` |
-| `POST` | `/keys/batch` | Batch insert/update | `[{"key": "k1", "value": "v1"}, ...]` |
-
-### Search & Monitoring
+## ๐ REST API Examples
| Method | Endpoint | Description |
|--------|----------|-------------|
-| `GET` | `/keys/search/prefix?q=user:` | Prefix search |
-| `GET` | `/keys/search/substring?q=alice` | Substring search |
+| `POST` | `/keys` | Insert/Update: `{"key": "k1", "value": "v1"}` |
+| `GET` | `/keys/{key}` | Retrieve value |
| `GET` | `/stats/all` | Full telemetry (Memory, Disk, WAL) |
-| `GET` | `/stats/memory` | MemTable statistics |
-| `GET` | `/stats/disk` | SSTable statistics |
-
-### Feature Flags
-
-| Method | Endpoint | Description |
-|--------|----------|-------------|
-| `GET` | `/features` | List all feature flags |
-| `POST` | `/features/{id}` | Create or update flag | `{"enabled": true}` |
-| `GET` | `/features/{id}` | Get flag status |
-
-## โ๏ธ Configuration
-
-ApexStore uses environment variables for configuration. No recompilation needed!
-
-### Quick Configuration Examples
-
-#### Stress Testing Profile
-```bash
-# .env
-MAX_JSON_PAYLOAD_SIZE=104857600 # 100MB
-MEMTABLE_MAX_SIZE=16777216 # 16MB
-BLOCK_CACHE_SIZE_MB=256
-SERVER_WORKERS=16
-```
-
-#### High Write Throughput
-```bash
-MEMTABLE_MAX_SIZE=8388608 # 8MB
-COMPACTION_STRATEGY=tiered
-WAL_SYNC_MODE=async_batch
-BLOCK_SIZE=8192
-```
-
-#### Memory Constrained
-```bash
-MEMTABLE_MAX_SIZE=2097152 # 2MB
-BLOCK_CACHE_SIZE_MB=32
-SPARSE_INDEX_INTERVAL=32
-BLOOM_FALSE_POSITIVE_RATE=0.02
-```
-
-For detailed configuration options, see [`docs/CONFIGURATION.md`](docs/CONFIGURATION.md).
## ๐ Project Structure
-Organized following **SOLID principles**:
-
```
ApexStore/
โโโ src/
-โ โโโ core/ # Domain logic (SRP)
-โ โ โโโ engine.rs # LSM Engine orchestration
-โ โ โโโ memtable.rs # In-memory storage
-โ โ โโโ log_record.rs # Data model
-โ โโโ storage/ # Persistence (DIP)
-โ โ โโโ wal.rs # Write-Ahead Log
-โ โ โโโ sstable.rs # SSTable reader/manager
-โ โ โโโ builder.rs # SSTable V2 builder
-โ โโโ infra/ # Cross-cutting concerns
-โ โ โโโ codec.rs # Serialization (Bincode)
-โ โ โโโ error.rs # Error handling
-โ โ โโโ config.rs # Configuration
-โ โโโ api/ # HTTP transport (Actix-Web)
-โ โ โโโ handlers.rs # REST endpoints
-โ โ โโโ server.rs # Server setup
-โ โ โโโ config.rs # Server config
-โ โโโ cli/ # Interactive interface
-โ โ โโโ repl.rs # REPL implementation
-โ โโโ features/ # Business domain
-โ โโโ flags.rs # Feature flag management
-โโโ docs/ # Documentation
-โ โโโ CONFIGURATION.md # Configuration guide
-โ โโโ CONTRIBUTING.md # Contribution guidelines
-โ โโโ SETUP.md # Development setup
-โโโ tests/ # Integration tests
-โโโ docker-compose.yml # Docker Compose configuration
-โโโ Dockerfile # Container build instructions
-โโโ .env.example # Configuration template
-โโโ Cargo.toml # Dependencies
-โโโ CHANGELOG.md # Version history
-โโโ README.md # This file
+โ โโโ core/ # LSM Engine, MemTable, Domain logic
+โ โโโ storage/ # WAL, SSTable V2, Block Builder
+โ โโโ infra/ # Codec, Error Handling, Config
+โ โโโ api/ # Actix-Web Server & Handlers
+โ โโโ cli/ # REPL Implementation
+โโโ docs/ # Detailed documentation & Architecture
+โโโ tests/ # Integration test suite
+โโโ Dockerfile # Multi-stage build
```
-## ๐งช Testing
+## ๐งช Testing & Quality
```bash
-# Run all tests
-cargo test
-
-# Run with output
-cargo test -- --nocapture
-
-# Run specific test
-cargo test test_builder_basic
-
-# Check code quality
-cargo clippy -- -D warnings
-
-# Format code
-cargo fmt
+cargo test # Run all tests
+cargo clippy -- -D warnings # Linting
+cargo fmt # Formatting
```
-## ๐ Performance Characteristics
-
-### Write Performance
-- **Sequential Writes**: ~500k ops/sec (in-memory MemTable)
-- **With WAL**: ~100k ops/sec (fsync overhead)
-- **Batch Writes**: Up to 1M ops/sec
-
-### Read Performance
-- **MemTable Hits**: ~1M ops/sec (BTreeMap lookup)
-- **SSTable Reads**: ~50k ops/sec (with Bloom filter)
-- **Cold Reads**: ~10k ops/sec (disk I/O)
-
-### Storage Efficiency
-- **Compression Ratio**: 2-4x with LZ4
-- **Memory Overhead**: ~100 bytes per MemTable entry
-- **Disk Amplification**: ~2-3x (before compaction)
-
-*Note: Benchmarks on AMD Ryzen 9 5900X, NVMe SSD. Your mileage may vary.*
-
## ๐บ๏ธ Roadmap
-### โ
Completed (v1.0 - v1.4)
-- [x] Core LSM engine with MemTable and WAL
-- [x] SSTable V2 with sparse indexing and compression
-- [x] REST API with feature flags
-- [x] Comprehensive configuration system
-- [x] Interactive CLI
-- [x] Bloom filters for read optimization
-- [x] Statistics and monitoring
-- [x] Global block cache
-- [x] Docker support with health checks
-
-### ๐ง In Progress (v1.5)
-- [ ] Storage iterators for range queries
-- [ ] Concurrent read optimization
-- [ ] Comprehensive benchmark suite
-
-### ๐ฎ Future (v2.0+)
-- [ ] Compaction strategies (Leveled, Tiered, Lazy Leveling)
-- [ ] Authentication & authorization
-- [ ] Data integrity validation (CRC32 checksums)
-- [ ] Multi-instance support
-- [ ] Secondary indexes
-- [ ] Snapshot isolation
-- [ ] Replication support
-- [ ] Distributed consensus (Raft)
-
-See [`ROADMAP.md`](ROADMAP.md) for detailed timeline.
+- [x] SSTable V2 with compression & Bloom Filters
+- [x] REST API & Feature Flags
+- [x] Global Block Cache
+- [ ] **v1.5**: Storage iterators for range queries
+- [ ] **v1.6**: Concurrent read optimization
+- [ ] **v2.0**: Leveled/Tiered Compaction Strategies
## ๐ค Contributing
-Contributions are welcome! Please read our [Contributing Guidelines](docs/CONTRIBUTING.md) before submitting PRs.
-
-### Quick Contribution Workflow
+Contributions are what make the open-source community an amazing place! Please check our [Contributing Guidelines](docs/CONTRIBUTING.md).
-1. Fork the repository
-2. Create a feature branch (`git checkout -b feature/amazing-feature`)
-3. Make your changes
-4. Run tests and linter (`cargo test && cargo clippy`)
-5. Commit your changes (`git commit -m 'feat: add amazing feature'`)
-6. Push to your branch (`git push origin feature/amazing-feature`)
-7. Open a Pull Request
+1. Fork the Project
+2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
+3. Commit your Changes (`git commit -m 'feat: add amazing feature'`)
+4. Push to the Branch (`git push origin feature/AmazingFeature`)
+5. Open a Pull Request
## ๐ License
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
-
-## ๐ Acknowledgments
-
-- **RocksDB**: Inspiration for LSM-Tree implementation
-- **LevelDB**: SSTable format reference
-- **Rust Community**: Amazing ecosystem and tooling
+Distributed under the MIT License. See `LICENSE` for more information.
## ๐ง Contact
-- **Author**: Elio Neto
-- **Email**: netoo.elio@hotmail.com
-- **GitHub**: [@ElioNeto](https://github.com/ElioNeto)
-- **Project**: [ApexStore](https://github.com/ElioNeto/ApexStore)
-- **Demo**: [lsm-admin-dev.up.railway.app](https://lsm-admin-dev.up.railway.app/)
+**Elio Neto** - [GitHub](https://github.com/ElioNeto) - netoo.elio@hotmail.com
+**Demo**: [lsm-admin-dev.up.railway.app](https://lsm-admin-dev.up.railway.app/)
## ๐ Star History
-If you find this project useful, please consider giving it a star! โญ
+[](https://star-history.com/#ElioNeto/ApexStore&Date)
---
-
-**Built with ๐ฆ Rust and โค๏ธ for high-performance storage systems**
+Built with ๐ฆ Rust and โค๏ธ for high-performance storage systems
From e69fcf3a2619482213ebae6800aee011754cb1b9 Mon Sep 17 00:00:00 2001
From: Elio
Date: Sat, 7 Mar 2026 21:59:19 -0300
Subject: [PATCH 03/11] feat: add logo
---
docs/assets/logo.png | Bin 0 -> 37006 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 docs/assets/logo.png
diff --git a/docs/assets/logo.png b/docs/assets/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..b540b732e358fad695a16f21bfd7233cf1a682e7
GIT binary patch
literal 37006
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
Date: Sat, 7 Mar 2026 22:04:18 -0300
Subject: [PATCH 04/11] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 9291da4..7000439 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@
+
---
From db424a867a9caeb3f0a75594d907e3370d90caa0 Mon Sep 17 00:00:00 2001
From: Elio Neto
Date: Sat, 7 Mar 2026 22:13:51 -0300
Subject: [PATCH 05/11] Translate CLI texts to English and update logo to
ApexStore
---
src/cli/mod.rs | 180 ++++++++++++++++++++++++-------------------------
1 file changed, 90 insertions(+), 90 deletions(-)
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index 073186b..f69d4b4 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -3,30 +3,30 @@ use std::io::{self, Write};
use std::path::PathBuf;
pub fn main() -> Result<(), Box> {
- // Configurar tracing
+ // Configure tracing
tracing_subscriber::fmt()
.with_target(false)
.with_level(true)
.init();
- println!(" ___ _____ __");
- println!(" / | ____ ___ _ ______/ ___// /_____ Jl");
- println!(r" / /| | / __ \/ _ \ |/_/___/\__ \/ __/ __ \/ __/");
- println!(r" / ___ |/ /_/ / __/> < ___/ / /_/ /_/ / /_");
- println!(r"/_/ |_/ .___/\___/_/|_| /____/\__/\____/\__/");
+ println!(" ___ _____ __ ");
+ println!(" / | ____ ___ _ __ / ___// /_____ ________ ");
+ println!(r" / /| | / __ \/ _ \| |/_/ \__ \/ __/ __ \/ ___/ _ \");
+ println!(r" / ___ |/ /_/ / __/> < ___/ / /_/ /_/ / / / __/");
+ println!(r"/_/ |_/ .___/\___/_/|_| /____/\__/\____/_/ \___/ ");
println!(" /_/ High-Performance LSM-Tree Engine\n");
- // Configuraรงรฃo
+ // Configuration
let config = LsmConfig::builder()
.dir_path(PathBuf::from("./.lsm_data"))
- .memtable_max_size(4 * 1024) // 4KB para testes
+ .memtable_max_size(4 * 1024) // 4KB for tests
.build()?;
- println!("๐ Diretรณrio de dados: {}", config.core.dir_path.display());
+ println!("๐ Data directory: {}", config.core.dir_path.display());
- println!("Inicializando engine...");
+ println!("Initializing engine...");
let engine = LsmEngine::new(config)?;
- println!("โ Engine inicializado com sucesso!\n");
+ println!("โ Engine initialized successfully!\n");
print_help();
println!();
@@ -50,21 +50,21 @@ pub fn main() -> Result<(), Box> {
match command.as_str() {
"SET" => {
if parts.len() < 3 {
- println!("โ Uso: SET ");
+ println!("โ Usage: SET ");
continue;
}
let key = parts[1].to_string();
let value = parts[2].as_bytes().to_vec();
match engine.set(key.clone(), value) {
- Ok(_) => println!("โ SET '{}' executado com sucesso", key),
- Err(e) => println!("โ Erro: {}", e),
+ Ok(_) => println!("โ SET '{}' executed successfully", key),
+ Err(e) => println!("โ Error: {}", e),
}
}
"GET" => {
if parts.len() < 2 {
- println!("โ Uso: GET ");
+ println!("โ Usage: GET ");
continue;
}
let key = parts[1];
@@ -74,21 +74,21 @@ pub fn main() -> Result<(), Box> {
let value_str = String::from_utf8_lossy(&value);
println!("โ '{}' = '{}'", key, value_str);
}
- Ok(None) => println!("โ Chave '{}' nรฃo encontrada", key),
- Err(e) => println!("โ Erro: {}", e),
+ Ok(None) => println!("โ Key '{}' not found", key),
+ Err(e) => println!("โ Error: {}", e),
}
}
"DELETE" | "DEL" => {
if parts.len() < 2 {
- println!("โ Uso: DELETE ");
+ println!("โ Usage: DELETE ");
continue;
}
let key = parts[1].to_string();
match engine.delete(key.clone()) {
- Ok(_) => println!("โ DELETE '{}' executado (tombstone criado)", key),
- Err(e) => println!("โ Erro: {}", e),
+ Ok(_) => println!("โ DELETE '{}' executed (tombstone created)", key),
+ Err(e) => println!("โ Error: {}", e),
}
}
@@ -98,9 +98,9 @@ pub fn main() -> Result<(), Box> {
match engine.stats_all() {
Ok(stats) => match serde_json::to_string_pretty(&stats) {
Ok(json) => println!("{}", json),
- Err(e) => println!("โ Erro ao serializar JSON: {}", e),
+ Err(e) => println!("โ Error serializing JSON: {}", e),
},
- Err(e) => println!("โ Erro: {}", e),
+ Err(e) => println!("โ Error: {}", e),
}
} else {
println!("{}", engine.stats());
@@ -109,7 +109,7 @@ pub fn main() -> Result<(), Box> {
"SEARCH" => {
if parts.len() < 2 {
- println!("โ Uso: SEARCH [--prefix]");
+ println!("โ Usage: SEARCH [--prefix]");
continue;
}
@@ -125,16 +125,16 @@ pub fn main() -> Result<(), Box> {
match results {
Ok(records) => {
if records.is_empty() {
- println!("โ Nenhum registro encontrado");
+ println!("โ No records found");
} else {
- println!("โ {} registro(s) encontrado(s):\n", records.len());
+ println!("โ {} record(s) found:\n", records.len());
for (key, value) in records {
let value_str = String::from_utf8_lossy(&value);
println!(" {} = {}", key, value_str);
}
}
}
- Err(e) => println!("โ Erro: {}", e),
+ Err(e) => println!("โ Error: {}", e),
}
}
@@ -150,7 +150,7 @@ pub fn main() -> Result<(), Box> {
}
"EXIT" | "QUIT" | "Q" => {
- println!("๐ Encerrando LSM-Tree CLI...");
+ println!("๐ Closing LSM-Tree CLI...");
break;
}
@@ -162,7 +162,7 @@ pub fn main() -> Result<(), Box> {
if parts.len() >= 3 && parts[1].to_uppercase() == "SET" {
// BATCH SET
let file_path = parts[2];
- println!("Importando de {}...", file_path);
+ println!("Importing from {}...", file_path);
match std::fs::read_to_string(file_path) {
Ok(content) => {
@@ -182,37 +182,37 @@ pub fn main() -> Result<(), Box> {
match engine.set(key.to_string(), value.as_bytes().to_vec()) {
Ok(_) => count += 1,
Err(e) => {
- println!("โ Erro na linha {}: {}", line_num + 1, e);
+ println!("โ Error on line {}: {}", line_num + 1, e);
errors += 1;
}
}
} else {
println!(
- "โ Linha {} invรกlida (formato esperado: key=value)",
+ "โ Invalid line {} (expected format: key=value)",
line_num + 1
);
errors += 1;
}
}
- println!("โ {} registro(s) importado(s)", count);
+ println!("โ {} record(s) imported", count);
if errors > 0 {
- println!("โ {} erro(s) encontrado(s)", errors);
+ println!("โ {} error(s) found", errors);
}
}
- Err(e) => println!("โ Erro ao ler arquivo: {}", e),
+ Err(e) => println!("โ Error reading file: {}", e),
}
} else if parts.len() >= 2 {
// BATCH (existing functionality)
let count: usize = match parts[1].parse() {
Ok(n) => n,
Err(_) => {
- println!("โ Count invรกlido");
+ println!("โ Invalid count");
continue;
}
};
- println!("Inserindo {} registros...", count);
+ println!("Inserting {} records...", count);
let start = std::time::Instant::now();
for i in 0..count {
@@ -222,16 +222,16 @@ pub fn main() -> Result<(), Box> {
}
let elapsed = start.elapsed();
- println!("โ {} registros inseridos em {:.2?}", count, elapsed);
- println!(" Taxa: {:.0} ops/s", count as f64 / elapsed.as_secs_f64());
+ println!("โ {} records inserted in {:.2?}", count, elapsed);
+ println!(" Rate: {:.0} ops/s", count as f64 / elapsed.as_secs_f64());
} else {
- println!("โ Uso: BATCH | BATCH SET ");
+ println!("โ Usage: BATCH | BATCH SET ");
}
}
"SCAN" => {
if parts.len() < 2 {
- println!("โ Uso: SCAN ");
+ println!("โ Usage: SCAN ");
continue;
}
let prefix = parts[1];
@@ -240,10 +240,10 @@ pub fn main() -> Result<(), Box> {
match engine.search_prefix(prefix) {
Ok(records) => {
if records.is_empty() {
- println!("โ Nenhum registro encontrado com prefixo '{}'", prefix);
+ println!("โ No records found with prefix '{}'", prefix);
} else {
println!(
- "โ {} registro(s) com prefixo '{}':\n",
+ "โ {} record(s) with prefix '{}':\n",
records.len(),
prefix
);
@@ -253,19 +253,19 @@ pub fn main() -> Result<(), Box> {
}
}
}
- Err(e) => println!("โ Erro: {}", e),
+ Err(e) => println!("โ Error: {}", e),
}
}
"ALL" => {
- println!("Listando todos os registros...\n");
+ println!("Listing all records...\n");
match engine.scan() {
Ok(records) => {
if records.is_empty() {
- println!("โ Banco de dados vazio");
+ println!("โ Database is empty");
} else {
println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
- println!("โ Chave โ Valor โ");
+ println!("โ Key โ Value โ");
println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค");
for (key, value) in records {
@@ -286,32 +286,32 @@ pub fn main() -> Result<(), Box> {
println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
}
}
- Err(e) => println!("โ Erro ao escanear: {}", e),
+ Err(e) => println!("โ Error scanning: {}", e),
}
}
"KEYS" => match engine.keys() {
Ok(keys) => {
if keys.is_empty() {
- println!("โ Nenhuma chave encontrada");
+ println!("โ No keys found");
} else {
- println!("Total de chaves: {}\n", keys.len());
+ println!("Total keys: {}\n", keys.len());
for (i, key) in keys.iter().enumerate() {
println!(" {}. {}", i + 1, key);
}
}
}
- Err(e) => println!("โ Erro: {}", e),
+ Err(e) => println!("โ Error: {}", e),
},
"COUNT" => match engine.count() {
- Ok(count) => println!("โ Total de registros ativos: {}", count),
- Err(e) => println!("โ Erro: {}", e),
+ Ok(count) => println!("โ Total active records: {}", count),
+ Err(e) => println!("โ Error: {}", e),
},
_ => {
- println!("โ Comando desconhecido: '{}'", command);
- println!(" Digite HELP para ver comandos disponรญveis");
+ println!("โ Unknown command: '{}'", command);
+ println!(" Type HELP to see available commands");
}
}
}
@@ -320,36 +320,36 @@ pub fn main() -> Result<(), Box> {
}
fn print_help() {
- println!("Comandos disponรญveis:");
- println!(" SET - Insere ou atualiza um par chave-valor");
- println!(" GET - Recupera o valor de uma chave");
- println!(" DELETE - Remove uma chave (cria tombstone)");
- println!(" SEARCH [--prefix] - Busca registros (opcionalmente por prefixo)");
- println!(" SCAN - Lista registros com prefixo especรญfico");
- println!(" ALL - Lista todos os registros do banco");
- println!(" KEYS - Lista apenas as chaves");
- println!(" COUNT - Conta registros ativos");
- println!(" STATS [ALL] - Exibe estatรญsticas (bรกsicas ou detalhadas)");
- println!(" BATCH - Insere N registros de teste");
- println!(" BATCH SET - Importa registros de arquivo");
- println!(" DEMO - Executa demonstraรงรฃo de features");
- println!(" CLEAR - Limpa a tela");
- println!(" HELP ou ? - Exibe esta ajuda");
- println!(" EXIT, QUIT ou Q - Sai do programa");
+ println!("Available commands:");
+ println!(" SET - Insert or update a key-value pair");
+ println!(" GET - Retrieve the value of a key");
+ println!(" DELETE - Remove a key (creates tombstone)");
+ println!(" SEARCH [--prefix] - Search records (optionally by prefix)");
+ println!(" SCAN - List records with specific prefix");
+ println!(" ALL - List all database records");
+ println!(" KEYS - List only the keys");
+ println!(" COUNT - Count active records");
+ println!(" STATS [ALL] - Display statistics (basic or detailed)");
+ println!(" BATCH - Insert N test records");
+ println!(" BATCH SET - Import records from file");
+ println!(" DEMO - Run feature demonstration");
+ println!(" CLEAR - Clear the screen");
+ println!(" HELP or ? - Display this help");
+ println!(" EXIT, QUIT or Q - Exit the program");
}
fn run_demo(engine: &LsmEngine) -> Result<(), Box> {
println!("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
- println!("โ DEMO AUTOMรTICA โ");
+ println!("โ AUTOMATIC DEMO โ");
println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
- println!("1. Inserindo dados de exemplo...");
+ println!("1. Inserting sample data...");
engine.set("user:alice".to_string(), b"Alice Silva".to_vec())?;
engine.set("user:bob".to_string(), b"Bob Santos".to_vec())?;
engine.set("user:charlie".to_string(), b"Charlie Costa".to_vec())?;
- println!(" โ 3 usuรกrios inseridos\n");
+ println!(" โ 3 users inserted\n");
- println!("2. Lendo dados...");
+ println!("2. Reading data...");
if let Some(v) = engine.get("user:alice")? {
println!(" user:alice = {}", String::from_utf8_lossy(&v));
}
@@ -358,66 +358,66 @@ fn run_demo(engine: &LsmEngine) -> Result<(), Box> {
}
println!();
- println!("3. Atualizando user:alice...");
+ println!("3. Updating user:alice...");
engine.set("user:alice".to_string(), b"Alice Silva Santos".to_vec())?;
if let Some(v) = engine.get("user:alice")? {
println!(
- " user:alice = {} (atualizado)",
+ " user:alice = {} (updated)",
String::from_utf8_lossy(&v)
);
}
println!();
- println!("4. Deletando user:bob...");
+ println!("4. Deleting user:bob...");
engine.delete("user:bob".to_string())?;
match engine.get("user:bob")? {
- Some(_) => println!(" โ Erro: ainda existe"),
- None => println!(" โ user:bob deletado com sucesso"),
+ Some(_) => println!(" โ Error: still exists"),
+ None => println!(" โ user:bob deleted successfully"),
}
println!();
- println!("5. Forรงando mรบltiplas escritas para flush...");
+ println!("5. Forcing multiple writes to trigger flush...");
for i in 0..10 {
engine.set(
format!("product:{}", i),
format!(
- "Product {} - Descriรงรฃo longa para forรงar flush automรกtico",
+ "Product {} - Long description to force automatic flush",
i
)
.into_bytes(),
)?;
}
- println!(" โ 10 produtos inseridos\n");
+ println!(" โ 10 products inserted\n");
- println!("6. Testando novos comandos...");
+ println!("6. Testing new commands...");
println!(" - SEARCH user:");
match engine.search("user:") {
- Ok(results) => println!(" Encontrados {} registros", results.len()),
- Err(e) => println!(" Erro: {}", e),
+ Ok(results) => println!(" Found {} records", results.len()),
+ Err(e) => println!(" Error: {}", e),
}
println!(" - SEARCH user: --prefix");
match engine.search_prefix("user:") {
- Ok(results) => println!(" Encontrados {} registros", results.len()),
- Err(e) => println!(" Erro: {}", e),
+ Ok(results) => println!(" Found {} records", results.len()),
+ Err(e) => println!(" Error: {}", e),
}
println!();
- println!("7. Estatรญsticas finais (bรกsicas):");
+ println!("7. Final statistics (basic):");
println!("{}", engine.stats());
- println!("\n8. Estatรญsticas detalhadas:");
+ println!("\n8. Detailed statistics:");
match engine.stats_all() {
Ok(stats) => {
if let Ok(json) = serde_json::to_string_pretty(&stats) {
println!("{}", json);
}
}
- Err(e) => println!(" Erro: {}", e),
+ Err(e) => println!(" Error: {}", e),
}
println!("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
- println!("โ DEMO CONCLUรDA โ");
+ println!("โ DEMO COMPLETED โ");
println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
Ok(())
From f4cc0fff689b716001c4708f32988c86b70e7e1a Mon Sep 17 00:00:00 2001
From: Elio
Date: Sat, 7 Mar 2026 22:22:14 -0300
Subject: [PATCH 06/11] fix: logo cli
---
src/cli/mod.rs | 23 ++++++-----------------
1 file changed, 6 insertions(+), 17 deletions(-)
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index f69d4b4..bb67e9e 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -9,9 +9,9 @@ pub fn main() -> Result<(), Box> {
.with_level(true)
.init();
- println!(" ___ _____ __ ");
- println!(" / | ____ ___ _ __ / ___// /_____ ________ ");
- println!(r" / /| | / __ \/ _ \| |/_/ \__ \/ __/ __ \/ ___/ _ \");
+ println!(" ___ _____ __ ");
+ println!(" / | ____ ___ _ __ / ___// /_____ _____ ___ ");
+ println!(r" / /| | / __ \/ _ \| |/_/ \__ \/ __/ __ \/ ___// _ \");
println!(r" / ___ |/ /_/ / __/> < ___/ / /_/ /_/ / / / __/");
println!(r"/_/ |_/ .___/\___/_/|_| /____/\__/\____/_/ \___/ ");
println!(" /_/ High-Performance LSM-Tree Engine\n");
@@ -242,11 +242,7 @@ pub fn main() -> Result<(), Box> {
if records.is_empty() {
println!("โ No records found with prefix '{}'", prefix);
} else {
- println!(
- "โ {} record(s) with prefix '{}':\n",
- records.len(),
- prefix
- );
+ println!("โ {} record(s) with prefix '{}':\n", records.len(), prefix);
for (key, value) in records {
let value_str = String::from_utf8_lossy(&value);
println!(" {} = {}", key, value_str);
@@ -361,10 +357,7 @@ fn run_demo(engine: &LsmEngine) -> Result<(), Box> {
println!("3. Updating user:alice...");
engine.set("user:alice".to_string(), b"Alice Silva Santos".to_vec())?;
if let Some(v) = engine.get("user:alice")? {
- println!(
- " user:alice = {} (updated)",
- String::from_utf8_lossy(&v)
- );
+ println!(" user:alice = {} (updated)", String::from_utf8_lossy(&v));
}
println!();
@@ -380,11 +373,7 @@ fn run_demo(engine: &LsmEngine) -> Result<(), Box> {
for i in 0..10 {
engine.set(
format!("product:{}", i),
- format!(
- "Product {} - Long description to force automatic flush",
- i
- )
- .into_bytes(),
+ format!("Product {} - Long description to force automatic flush", i).into_bytes(),
)?;
}
println!(" โ 10 products inserted\n");
From 97d9f9d5eabe7b4bde34c69ddbcb57330f719b16 Mon Sep 17 00:00:00 2001
From: Elio Neto
Date: Sat, 7 Mar 2026 22:45:04 -0300
Subject: [PATCH 07/11] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 7000439..7c9deaf 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-
+
From 1ae9d24ba628a66c353621f38364561ba0397c74 Mon Sep 17 00:00:00 2001
From: Elio
Date: Sat, 7 Mar 2026 23:50:45 -0300
Subject: [PATCH 08/11] feat: publish
---
Cargo.lock | 2 +-
Cargo.toml | 25 ++++++++++++++++++++-----
2 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index f839185..97d00b1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -271,7 +271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
-name = "apexstore"
+name = "apex-store-rs"
version = "2.1.0"
dependencies = [
"actix-cors",
diff --git a/Cargo.toml b/Cargo.toml
index c25eea9..ff97d6b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,22 +1,36 @@
[package]
-name = "apexstore"
+name = "apex-store-rs" # Alterado para ser รบnico no crates.io
version = "2.1.0"
edition = "2021"
authors = ["Elio Neto "]
license = "MIT"
repository = "https://github.com/ElioNeto/ApexStore"
-description = "A high-performance LSM-tree storage engine with SSTable V2 format"
+homepage = "https://github.com/ElioNeto/ApexStore"
+documentation = "https://elioneto.github.io/ApexStore/"
+readme = "README.md"
+description = "A high-performance, embedded LSM-tree storage engine with SSTable V2 format, LZ4 compression, and Bloom Filters."
+keywords = ["database", "lsm-tree", "key-value", "storage-engine", "embedded"]
+categories = ["database-implementations", "data-structures"]
+# Impede que arquivos de banco de dados locais sejam enviados no pacote
+exclude = [
+ "data/*",
+ "target/*",
+ "*.sst",
+ "*.log",
+ ".env",
+ "docs/assets/*"
+]
[[bin]]
name = "apexstore-server"
path = "src/bin/server.rs"
[[bin]]
-name = "cli"
+name = "apexstore-cli" # Renomeado de 'cli' para evitar conflitos globais
path = "src/bin/cli.rs"
[lib]
-name = "apexstore"
+name = "apexstore" # Mantido para que vocรช use `use apexstore;` no seu cรณdigo
path = "src/lib.rs"
[features]
@@ -55,6 +69,7 @@ criterion = { version = "0.5", features = ["html_reports"] }
opt-level = 3
lto = true
codegen-units = 1
+panic = "abort" # Opcional: reduz o tamanho do binรกrio
[profile.bench]
-inherits = "release"
+inherits = "release"
\ No newline at end of file
From 562bb4666726914d96bf684babfcb70ebb4b5933 Mon Sep 17 00:00:00 2001
From: Elio
Date: Sun, 8 Mar 2026 14:11:26 -0300
Subject: [PATCH 09/11] fix(cli): modify parts variable to collect data
corretly
---
src/cli/mod.rs | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index bb67e9e..ac08f8d 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -33,7 +33,7 @@ pub fn main() -> Result<(), Box> {
// REPL Loop
loop {
- print!("lsm> ");
+ print!("ApexStore (CLI): ");
io::stdout().flush()?;
let mut input = String::new();
@@ -44,7 +44,7 @@ pub fn main() -> Result<(), Box> {
continue;
}
- let parts: Vec<&str> = input.splitn(4, ' ').collect();
+ let parts: Vec<&str> = input.splitn(3, ' ').collect();
let command = parts[0].to_uppercase();
match command.as_str() {
@@ -55,6 +55,7 @@ pub fn main() -> Result<(), Box> {
}
let key = parts[1].to_string();
let value = parts[2].as_bytes().to_vec();
+ //let value = parts[2..].join(" ").as_bytes().to_vec();
match engine.set(key.clone(), value) {
Ok(_) => println!("โ SET '{}' executed successfully", key),
@@ -144,13 +145,16 @@ pub fn main() -> Result<(), Box> {
"CLEAR" => {
print!("\x1B[2J\x1B[1;1H"); // Clear screen ANSI code
- println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
- println!("โ LSM-Tree Key-Value Store - Interactive CLI โ");
- println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
+ println!(" ___ _____ __ ");
+ println!(" / | ____ ___ _ __ / ___// /_____ _____ ___ ");
+ println!(r" / /| | / __ \/ _ \| |/_/ \__ \/ __/ __ \/ ___// _ \");
+ println!(r" / ___ |/ /_/ / __/> < ___/ / /_/ /_/ / / / __/");
+ println!(r"/_/ |_/ .___/\___/_/|_| /____/\__/\____/_/ \___/ ");
+ println!(" /_/ High-Performance LSM-Tree Engine\n");
}
"EXIT" | "QUIT" | "Q" => {
- println!("๐ Closing LSM-Tree CLI...");
+ println!("๐ Closing ApexStore... See you later");
break;
}
From e81a3739ed7a709717ecd68d4968db9d18251a66 Mon Sep 17 00:00:00 2001
From: Elio
Date: Sun, 8 Mar 2026 14:28:00 -0300
Subject: [PATCH 10/11] feat(#76): fixes #76
---
CHANGELOG_v1.5.md | 225 ----------------------------------------------
1 file changed, 225 deletions(-)
delete mode 100644 CHANGELOG_v1.5.md
diff --git a/CHANGELOG_v1.5.md b/CHANGELOG_v1.5.md
deleted file mode 100644
index 932afe9..0000000
--- a/CHANGELOG_v1.5.md
+++ /dev/null
@@ -1,225 +0,0 @@
-# Changelog v1.5 - CLI Command Parity
-
-## Release Date
-March 6, 2026
-
-## Overview
-This release equalizes the CLI command set with REST API endpoints, providing feature parity between both interfaces and enhancing the user experience.
-
-## New Features
-
-### 1. STATS ALL - Detailed Statistics
-**Command:** `STATS ALL`
-
-**Description:** Displays comprehensive statistics in JSON format including:
-- MemTable records and size
-- SSTable files, records, and disk usage
-- WAL size
-- Total record count
-- Configuration parameters
-
-**Example:**
-```bash
-lsm> STATS ALL
-{
- "mem_records": 3,
- "mem_kb": 2,
- "sst_files": 2,
- "sst_records": 47,
- "sst_kb": 156,
- "wal_kb": 1,
- "total_records": 50,
- "memtable_max_size": 4
-}
-```
-
-**REST API Equivalent:** `GET /stats/all`
-
-### 2. SEARCH - Advanced Search with Prefix Support
-**Command:** `SEARCH [--prefix]`
-
-**Description:** Searches for records matching a query with two modes:
-- **Substring mode** (default): Finds all keys containing the query string
-- **Prefix mode** (with `--prefix` flag): Finds all keys starting with the query string (optimized)
-
-**Examples:**
-```bash
-# Substring search
-lsm> SEARCH user
-โ 3 registro(s) encontrado(s):
- user:alice = Alice Silva
- user:bob = Bob Santos
- user:charlie = Charlie Costa
-
-# Prefix search (faster for hierarchical keys)
-lsm> SEARCH user: --prefix
-โ 3 registro(s) encontrado(s):
- user:alice = Alice Silva
- user:bob = Bob Santos
- user:charlie = Charlie Costa
-```
-
-**REST API Equivalent:** `GET /keys/search?q=query&prefix=true`
-
-### 3. BATCH SET - Bulk Import from Files
-**Command:** `BATCH SET `
-
-**Description:** Imports records from a text file in `key=value` format.
-
-**File Format:**
-```
-# Comments start with #
-user:alice=Alice Silva
-user:bob=Bob Santos
-product:1=Laptop Dell XPS 15
-config:theme=dark
-```
-
-**Features:**
-- Supports comments (lines starting with `#`)
-- Skips empty lines
-- Automatic key/value trimming
-- Error reporting with line numbers
-- Progress feedback
-
-**Example:**
-```bash
-lsm> BATCH SET examples/batch_data.txt
-Importando de examples/batch_data.txt...
-โ 23 registro(s) importado(s)
-```
-
-**REST API Equivalent:** `POST /keys/batch`
-
-### 4. SCAN Improvement
-**Command:** `SCAN `
-
-**Description:** Updated to use the optimized `search_prefix` method instead of showing "not implemented" warning.
-
-**Example:**
-```bash
-lsm> SCAN user:
-โ 3 registro(s) com prefixo 'user:':
- user:alice = Alice Silva
- user:bob = Bob Santos
- user:charlie = Charlie Costa
-```
-
-## Improvements
-
-### Enhanced Help System
-Updated `print_help()` function to include all new commands with proper formatting:
-```
-STATS [ALL] - Exibe estatรญsticas (bรกsicas ou detalhadas)
-SEARCH [--prefix] - Busca registros (opcionalmente por prefixo)
-BATCH SET - Importa registros de arquivo
-```
-
-### Enhanced DEMO Command
-Updated demo to showcase all new features:
-- Tests SEARCH in both modes
-- Displays STATS ALL output
-- Demonstrates prefix scanning
-
-## Documentation
-
-### New Files
-- **`docs/CLI_GUIDE.md`**: Comprehensive CLI documentation
- - All command syntax and examples
- - Best practices
- - Troubleshooting guide
- - Complete workflow examples
-
-- **`examples/batch_data.txt`**: Sample data file
- - Demonstrates file format for BATCH SET
- - Includes various data types (users, products, config)
- - Comments explaining usage
-
-### Updated Files
-- **`src/cli/mod.rs`**: Complete CLI implementation
-- **`CHANGELOG_v1.5.md`**: This changelog
-
-## Breaking Changes
-None. All existing commands remain unchanged and backward compatible.
-
-## Migration Guide
-No migration needed. New commands are additive.
-
-## Implementation Details
-
-### Command Parsing
-- Updated `splitn` to handle 4 parts for complex commands
-- Maintains backward compatibility with existing command syntax
-
-### Error Handling
-- Graceful handling of file read errors
-- Line-by-line error reporting for BATCH SET
-- Validates file format and provides helpful error messages
-
-### Performance
-- SEARCH with `--prefix` flag uses optimized BTree iteration
-- BATCH SET processes files line-by-line (memory efficient)
-- STATS ALL uses existing engine methods (minimal overhead)
-
-## Testing
-
-### Manual Testing Completed
-- [x] STATS ALL produces valid JSON
-- [x] SEARCH works in both substring and prefix modes
-- [x] BATCH SET imports data correctly
-- [x] File format validation works properly
-- [x] Error messages are helpful and accurate
-- [x] SCAN command uses optimized search
-- [x] All commands maintain backward compatibility
-
-### Test Files
-- `examples/batch_data.txt` - Sample data for testing
-
-## CLI vs REST API Command Mapping
-
-| CLI Command | REST API Endpoint | Status |
-|-------------|------------------|--------|
-| `SET ` | `POST /keys` | โ
Parity |
-| `GET ` | `GET /keys/{key}` | โ
Parity |
-| `DELETE ` | `DELETE /keys/{key}` | โ
Parity |
-| `STATS` | `GET /stats` | โ
Parity |
-| `STATS ALL` | `GET /stats/all` | โ
**New** |
-| `SEARCH ` | `GET /keys/search?q=...` | โ
**New** |
-| `SEARCH --prefix` | `GET /keys/search?q=...&prefix=true` | โ
**New** |
-| `BATCH SET ` | `POST /keys/batch` | โ
**New** |
-| `KEYS` | `GET /keys` | โ
Parity |
-| `ALL` | `GET /scan` | โ
Parity |
-| `COUNT` | N/A (CLI only) | โ
CLI-only |
-| `SCAN ` | `GET /scan?prefix=...` | โ
Improved |
-
-## Known Limitations
-
-1. **Token Management**: CLI does not support token management commands (low priority - better suited for REST API)
-2. **Feature Flags**: CLI does not yet support feature flag management (planned for future release)
-3. **Large Files**: BATCH SET loads entire file into memory (acceptable for typical use cases)
-
-## Future Enhancements (v1.6+)
-
-### Phase 2: Feature Management (Medium Priority)
-- [ ] `FEATURES` - List all feature flags
-- [ ] `FEATURE SET ` - Toggle feature flags
-
-### Phase 3: Advanced Features (Low Priority)
-- [ ] `EXPORT ` - Export data to file
-- [ ] `IMPORT [--format json|txt]` - Import with format detection
-- [ ] `TOKEN` commands - CLI-based token management
-
-## Contributors
-- Elio Neto (@ElioNeto)
-
-## Related Issues
-- Closes #65 - Equalize CLI Commands with REST API Endpoints
-
-## References
-- [CLI Guide](docs/CLI_GUIDE.md)
-- [REST API Documentation](docs/API.md)
-- [Configuration Guide](docs/CONFIGURATION.md)
-
----
-
-**Release Notes:** This release focuses on developer experience by providing CLI feature parity with the REST API. All Phase 1 (High Priority) commands have been implemented, tested, and documented.
From 42b4da689f6e9f77b88509a53ddc2c8089eb5c93 Mon Sep 17 00:00:00 2001
From: Elio
Date: Sun, 8 Mar 2026 14:36:40 -0300
Subject: [PATCH 11/11] fix: workflow
---
.github/workflows/feature-fix-workflow.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/feature-fix-workflow.yml b/.github/workflows/feature-fix-workflow.yml
index 6470756..961f865 100644
--- a/.github/workflows/feature-fix-workflow.yml
+++ b/.github/workflows/feature-fix-workflow.yml
@@ -199,7 +199,7 @@ jobs:
if [ "$ISSUE_STATE" = "OPEN" ]; then
# Get recent commits for this branch
- RECENT_COMMITS=$(git log "origin/develop..${{ github.ref_name }}" --pretty=format:'- %s (%h)' | head -3)
+ RECENT_COMMITS=$(git log "origin/develop..${{ github.ref_name }}"-n 3 --pretty=format:'- %s (%h)')
gh issue comment "$ISSUE_NUM" --body "๐ **Update from \`${{ github.ref_name }}\`**