# LuckDB: A Lightweight JSON Document Database in Rust with Encryption
[](https://crates.io/crates/luckdb)
[](https://gitlab.com/andrew_ryan/luckdb)
[](https://gitlab.com/andrew_ryan/luckdb/-/raw/master/LICENSE)
LuckDB is a lightweight, in-memory JSON document database written in Rust with built-in AES-256 encryption and configuration management. It provides a simple yet powerful API for storing, querying, and manipulating JSON documents with support for indexing, aggregation, persistence, and secure client-server mode.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Client](#client)
- [Database](#database)
- [Collection](#collection)
- [Configuration](#configuration)
- [Encryption](#encryption)
- [Server](#server)
- [Documents](#documents)
- [Queries](#queries)
- [Updates](#updates)
- [Indexes](#indexes)
- [Aggregation](#aggregation)
- [Examples](#examples)
- [Basic CRUD Operations](#basic-crud-operations)
- [Advanced Querying](#advanced-querying)
- [Indexing](#indexing)
- [Aggregation Pipeline](#aggregation-pipeline)
- [Persistence](#persistence)
- [Configuration File Management](#configuration-file-management)
- [Server CLI Tool](#server-cli-tool)
- [Advanced Encryption](#advanced-encryption)
- [Client-Server Mode](#client-server-mode)
- [License](#license)
## Features
- **Document Storage**: Store JSON documents with automatic ID generation
- **AES-256 Encryption**: Built-in encryption for data at rest with secure key management
- **Configuration Management**: Flexible configuration system with TOML file support
- **Rich Querying**: Support for complex queries with multiple operators
- **Indexing**: Create indexes for improved query performance
- **Aggregation Pipeline**: Transform and analyze data with aggregation stages
- **Update Operations**: Flexible document updates with various operators
- **Persistence**: Save and load data from disk with optional encryption
- **Secure Client-Server Mode**: Networked access with authentication and encryption
- **Bulk Operations**: Efficient bulk write operations
- **Geospatial Queries**: Basic support for geospatial queries
- **Platform-specific Storage Paths**: Automatic handling of storage paths across operating systems
- **Configuration File Support**: TOML-based configuration with validation
- **Command-Line Interface**: Professional CLI tool for server management and deployment
## Installation
### As a Library
Add LuckDB to your `Cargo.toml`:
```toml
[dependencies]
luckdb = "0.1.3"
```
### CLI Server Tool
The LuckDB CLI server tool is included as part of the project. Build it from source:
```bash
# Clone the repository
git clone https://gitcode.net/dnrops/luckdb
cd luckdb
# Build the CLI tool
cargo build --release --bin luckdb-server-cli
# OR
cargo install luckdb
# The CLI binary will be available at:
# Linux/macOS: target/release/luckdb-server-cli
# Windows: target/release/luckdb-server-cli.exe
```
Alternatively, use the convenience scripts:
```bash
# Linux/macOS
chmod +x run-server.sh
./run-server.sh --help
# Windows
run-server.bat --help
```
## Quick Start
### Library Usage
#### Basic Usage
```rust
use luckdb::{Client, config::DatabaseConfig, Query};
use serde_json::json;
fn main() -> luckdb::Result<()> {
// Create a client with encryption and storage
let mut config = DatabaseConfig::with_storage_path("./data");
config.encryption_enabled = true;
config.encryption_password = Some("my_secure_password".to_string());
let mut client = Client::with_config(config)?;
// Get a database
let db = client.db("mydb");
// Get a collection
let collection = db.collection("users");
// Insert a document
let doc = json!({
"name": "Alice",
"age": 30,
"city": "New York",
"interests": ["reading", "hiking"]
});
let id = collection.insert(doc)?;
println!("Inserted document with ID: {}", id);
// Query the collection
let query = Query::new().eq("name", "Alice".into());
let results = collection.find(query, None)?;
for (id, doc) in results {
println!("Found document {}: {}", id, doc);
}
// Save with encryption
client.save()?;
Ok(())
}
```
### Encrypted Storage
```rust
use luckdb::{Client, config::DatabaseConfig};
// Create configuration with encryption
let config = DatabaseConfig::with_storage_path("./secure_data")
.with_encryption("my_password");
let mut client = Client::with_config(config)?;
// All data will be encrypted when saved to disk
client.save()?;
```
### Server Mode with Authentication
```rust
use luckdb::{Server, config::DatabaseConfig};
// Configure server with authentication and encryption
let server_config = DatabaseConfig::with_storage_path("./server_data")
.with_auth("admin", "secure_password")
.with_encryption("server_encryption_key");
let mut server = Server::with_config("127.0.0.1:27017".parse().unwrap(), server_config)?;
server.start()?;
```
### Configuration File
Create a `config.toml` file to configure LuckDB:
```toml
[luckdb]
# Storage path for database files (optional)
storage_path = "./data"
# Enable AES-256 encryption (default: false)
encryption_enabled = true
# Encryption password for data protection
encryption_password = "your_secure_password_here"
# Authentication for client-server mode (optional)
auth_username = "admin"
auth_password = "your_auth_password_here"
# Server address for client-server mode (optional)
server_address = "127.0.0.1:27017"
# Auto-save interval in seconds (optional, None = manual only)
auto_save_interval_seconds = 300
# Maximum number of backup files to keep (default: 5)
max_backup_files = 10
```
Then load the configuration:
```rust
use luckdb::{Client, config::DatabaseConfig};
// Load configuration from file
let config = DatabaseConfig::load_from_file("config.toml")?;
let mut client = Client::with_config(config)?;
// Or use the convenience method
let mut client = Client::with_config_file("config.toml")?;
```
### CLI Server Quick Start
#### Start a Development Server
```bash
# Quick development server (in-memory, debug logging)
luckdb-server-cli --dev
# Development server with file persistence
luckdb-server-cli --storage ./dev_data --log-level debug
# Development server with encryption
luckdb-server-cli --dev --encrypt --password "dev_password"
```
#### Start a Production Server
```bash
# Create production configuration
luckdb-server-cli config --output prod.toml --config-type prod
# Edit the configuration with your secure passwords
# Edit prod.toml to set real encryption and auth passwords
# Start server with configuration
luckdb-server-cli --config prod.toml
```
#### Start Server with Authentication
```bash
# Quick server with authentication
luckdb-server-cli --auth --username admin --auth-password "secure_password" --storage ./data
# Server with encryption and authentication
luckdb-server-cli \
--encrypt \
--password "encryption_password" \
--auth \
--username admin \
--auth-password "auth_password" \
--storage ./secure_data
```
#### Server Management Commands
```bash
# View help
luckdb-server-cli --help
# Generate configuration files
luckdb-server-cli config --output config.toml --config-type prod
# Check version
luckdb-server-cli version
```
## API Reference
### Client
The `Client` is the entry point to LuckDB. It manages multiple databases with configurable storage and encryption.
```rust
// Basic in-memory client
let mut client = Client::new();
// With configuration
let config = DatabaseConfig::with_storage_path("./data")
.with_encryption("password");
let mut client = Client::with_config(config)?;
// From configuration file
let mut client = Client::with_config_file("config.toml")?;
```
#### Methods
- `new() -> Client`: Create a basic in-memory client
- `with_config(config: DatabaseConfig) -> Result<Client>`: Create client with configuration
- `with_config_file(path: P) -> Result<Client>`: Create client from TOML configuration file
- `with_encryption(password: S) -> Result<Self>`: Enable encryption on existing client
- `db(&mut self, name: &str) -> &mut Database`: Get or create a database
- `list_database_names(&self) -> Vec<String>`: List all databases
- `drop_database(&mut self, name: &str) -> Result<()>`: Drop a database
- `save(&self) -> Result<()>`: Save all data to disk (encrypted if configured)
- `load(&mut self) -> Result<()>`: Load data from disk (decrypt if needed)
- `config(&self) -> &DatabaseConfig`: Get current configuration
- `update_config(&mut self, config: DatabaseConfig) -> Result<()>`: Update configuration
### Database
A `Database` contains multiple collections.
```rust
let db = client.db("mydb");
```
#### Methods
- `collection(&mut self, name: &str) -> &mut Collection`: Get or create a collection
- `list_collection_names(&self) -> Vec<String>`: List all collections
- `create_collection(&mut self, name: &str, options: Option<CreateCollectionOptions>) -> Result<()>`: Create a collection with options
- `drop_collection(&mut self, name: &str) -> Result<()>`: Drop a collection
- `stats(&self) -> Result<DatabaseStats>`: Get database statistics
- `run_command(&mut self, command: &Document) -> Result<Document>`: Run a database command
### Collection
A `Collection` stores JSON documents.
```rust
let collection = db.collection("users");
```
#### Methods
- `insert(&mut self, doc: Document) -> Result<DocId>`: Insert a document
- `insert_many(&mut self, docs: Vec<Document>) -> Result<Vec<DocId>>`: Insert multiple documents
- `find(&self, query: Query, options: Option<FindOptions>) -> Result<Vec<(DocId, Document)>>`: Find documents matching a query
- `find_one(&self, query: Query, options: Option<FindOptions>) -> Result<(DocId, Document)>`: Find a single document
- `update_one(&mut self, query: Query, update: UpdateDocument, upsert: bool) -> Result<usize>`: Update the first matching document
- `update_many(&mut self, query: Query, update: UpdateDocument) -> Result<usize>`: Update all matching documents
- `replace_one(&mut self, query: Query, replacement: Document, upsert: bool) -> Result<usize>`: Replace the first matching document
- `delete_one(&mut self, query: Query) -> Result<usize>`: Delete the first matching document
- `delete_many(&mut self, query: Query) -> Result<usize>`: Delete all matching documents
- `count_documents(&self, query: Query) -> Result<usize>`: Count documents matching a query
- `create_index(&mut self, index: Index) -> Result<()>`: Create an index
- `drop_index(&mut self, name: &str) -> Result<()>`: Drop an index
- `list_indexes(&self) -> Result<Vec<Index>>`: List all indexes
- `aggregate(&self, pipeline: Vec<AggregationStage>) -> Result<Vec<Document>>`: Run an aggregation pipeline
- `distinct(&self, field: &str, query: Option<Query>) -> Result<Vec<Value>>`: Get distinct values for a field
- `bulk_write(&mut self, operations: Vec<BulkWriteOperation>, options: Option<BulkWriteOptions>) -> Result<BulkWriteResult>`: Execute bulk write operations
### Configuration
The `DatabaseConfig` struct provides comprehensive configuration options for LuckDB.
```rust
use luckdb::config::DatabaseConfig;
// Default configuration
let config = DatabaseConfig::new();
// With storage path
let config = DatabaseConfig::with_storage_path("./data");
// With encryption
let config = DatabaseConfig::with_encryption("password");
// With authentication
let config = DatabaseConfig::new()
.with_auth("username", "password")
.with_storage_path("./data");
// Save/load configuration
config.save_to_file("config.toml")?;
let loaded_config = DatabaseConfig::load_from_file("config.toml")?;
```
#### Configuration Options
- `storage_path: Option<PathBuf>`: Path for database file storage (can be directory or specific file)
- `encryption_enabled: bool`: Enable/disable AES-256 encryption (default: false)
- `encryption_password: Option<String>`: Password for encryption key derivation
- `server_address: Option<String>`: Server address for client-server mode
- `auth_username: Option<String>`: Authentication username
- `auth_password: Option<String>`: Authentication password
- `auto_save_interval_seconds: Option<u64>`: Auto-save interval in seconds (None = manual only)
- `max_backup_files: usize`: Maximum number of backup files to keep (default: 5)
#### Configuration Methods
- `new() -> DatabaseConfig`: Create default configuration
- `with_storage_path(path: P) -> DatabaseConfig`: Set storage path
- `with_encryption(password: S) -> DatabaseConfig`: Enable encryption with password
- `with_auth<U: Into<String>, P: Into<String>>(username, password) -> DatabaseConfig`: Set authentication
- `with_server_mode<S: Into<String>>(address: S) -> DatabaseConfig`: Set server address
- `save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()>`: Save config to TOML file
- `load_from_file<P: AsRef<Path>>(path: P) -> Result<Self>`: Load config from TOML file
- `validate(&self) -> Result<()>`: Validate configuration settings
- `is_encryption_configured(&self) -> bool`: Check if encryption is properly configured
- `is_auth_configured(&self) -> bool`: Check if authentication is configured
- `get_or_create_storage_path(&self) -> Result<PathBuf>`: Get storage path, create parent dirs if needed
### Encryption
LuckDB provides built-in AES-256 encryption for secure data persistence.
```rust
use luckdb::encryption::{AesEncryption, EncryptionKey};
// Create encryption key from password
let key = EncryptionKey::from_password("my_password", b"salt");
// Encrypt data
let encrypted = AesEncryption::encrypt_string("sensitive data", &key)?;
// Decrypt data
let decrypted = AesEncryption::decrypt_string(&encrypted, &key)?;
```
#### Encryption Features
- **AES-256-CBC mode**: Strong encryption standard
- **PBKDF2-like key derivation**: Secure password-based key generation
- **HMAC authentication**: Data integrity verification
- **Random IV generation**: Unique encryption for each save
- **Base64 encoding**: Safe storage format
### Server
The `Server` provides networked access to LuckDB with authentication and encryption.
```rust
use luckdb::{Server, config::DatabaseConfig};
// Create server with configuration
let config = DatabaseConfig::with_storage_path("./server_data")
.with_auth("admin", "password")
.with_encryption("server_key");
let mut server = Server::with_config("127.0.0.1:27017".parse().unwrap(), config)?;
// Start server
server.start()?;
```
#### Server Features
- **TCP-based networking**: Standard network protocol
- **Authentication**: Username/password based access control
- **Encryption**: Data stored encrypted on server
- **Thread-per-connection**: Concurrent client handling
- **Command protocol**: Text-based commands for easy integration
### Documents
Documents are JSON values represented by `serde_json::Value`.
```rust
use serde_json::json;
let doc = json!({
"name": "Alice",
"age": 30,
"city": "New York",
"interests": ["reading", "hiking"]
});
```
Each document automatically gets an `_id` field of type `DocId` when inserted.
### Queries
Queries are built using the `Query` struct and its methods.
```rust
use luckdb::{Query, Value};
use serde_json::json;
// Simple equality query
let query = Query::new().eq("name", "Alice".into());
// Complex query with multiple conditions
let query = Query::new()
.eq("city", "New York".into())
.gt("age", json!(25))
.in_("interests", vec!["reading".into(), "hiking".into()]);
// Using logical operators
let query1 = Query::new().eq("city", "New York".into());
let query2 = Query::new().eq("city", "San Francisco".into());
let query = Query::new().or(vec![query1, query2]);
```
#### Query Operators
- `eq(key, value)`: Field equals value
- `ne(key, value)`: Field not equal to value
- `gt(key, value)`: Field greater than value
- `gte(key, value)`: Field greater than or equal to value
- `lt(key, value)`: Field less than value
- `lte(key, value)`: Field less than or equal to value
- `in_(key, values)`: Field in array of values
- `nin(key, values)`: Field not in array of values
- `exists(key, exists)`: Field exists (or not)
- `regex(key, pattern)`: Field matches regex pattern
- `and(queries)`: Logical AND of queries
- `or(queries)`: Logical OR of queries
- `nor(queries)`: Logical NOR of queries
- `not(query)`: Logical NOT of query
- `all(key, values)`: Array contains all values
- `elem_match(key, query)`: Array element matches query
- `size(key, size)`: Array has specified size
- `near(key, point, max_distance)`: Geospatial near query
- `within(key, shape)`: Geospatial within query
- `intersects(key, shape)`: Geospatial intersects query
### Updates
Update operations are built using the `UpdateDocument` struct.
```rust
use luckdb::UpdateDocument;
use serde_json::json;
let update = UpdateDocument::new()
.set("status", "active".into())
.inc("login_count", json!(1))
.push("tags", "premium".into());
```
#### Update Operators
- `set(key, value)`: Set field to value
- `unset(key)`: Remove field
- `inc(key, value)`: Increment field by value
- `mul(key, value)`: Multiply field by value
- `rename(old_key, new_key)`: Rename field
- `set_on_insert(key, value)`: Set field on insert
- `min(key, value)`: Set field to minimum of current and value
- `max(key, value)`: Set field to maximum of current and value
- `current_date(key, type_spec)`: Set field to current date
- `push(key, value)`: Push value to array field
- `push_all(key, values)`: Push all values to array field
- `add_to_set(key, value)`: Add value to array if not present
- `pop(key, pos)`: Remove first or last element of array
- `pull(key, condition)`: Remove elements matching condition
- `pull_all(key, values)`: Remove all specified values from array
- `bit(key, operation)`: Bitwise operation
### Indexes
Indexes improve query performance.
```rust
use luckdb::{Index, IndexType};
// Create a simple index
let index = Index::new("name_index".to_string(), vec![("name".to_string(), IndexType::Ascending)]);
collection.create_index(index)?;
// Create a compound index
let index = Index::new("compound_index".to_string(), vec![
("city".to_string(), IndexType::Ascending),
("age".to_string(), IndexType::Descending)
]);
collection.create_index(index)?;
// Create a unique index
let index = Index::new("email_index".to_string(), vec![("email".to_string(), IndexType::Ascending)])
.unique(true);
collection.create_index(index)?;
```
### Aggregation
Aggregation pipelines transform and analyze data.
```rust
use luckdb::{AggregationStage, GroupOperation, GroupId, SortOrder};
use serde_json::json;
let pipeline = vec![
// Match documents
AggregationStage::Match(Query::new().eq("status", "active".into())),
// Group by city and count
AggregationStage::Group(GroupSpecification {
id: GroupId::Field("city".to_string()),
operations: {
let mut ops = std::collections::HashMap::new();
ops.insert("count".to_string(), GroupOperation::Sum(json!(1)));
ops.insert("avg_age".to_string(), GroupOperation::Avg("$age".into()));
ops
},
}),
// Sort by count
AggregationStage::Sort(vec![("count".to_string(), SortOrder::Descending)]),
// Limit results
AggregationStage::Limit(10),
];
let results = collection.aggregate(pipeline)?;
```
## Examples
### Basic CRUD Operations
```rust
use luckdb::{Client, Query, UpdateDocument};
use serde_json::json;
fn main() -> luckdb::Result<()> {
let mut client = Client::new();
let db = client.db("test");
let collection = db.collection("users");
// Create
let doc = json!({
"name": "Alice",
"age": 30,
"city": "New York",
"interests": ["reading", "hiking"]
});
let id = collection.insert(doc)?;
println!("Created document with ID: {}", id);
// Read
let query = Query::new().eq("name", "Alice".into());
let results = collection.find(query, None)?;
for (id, doc) in results {
println!("Found document {}: {}", id, doc);
}
// Update
let update = UpdateDocument::new()
.set("age", json!(31))
.push("interests", "travel".into());
let count = collection.update_one(Query::new().eq("name", "Alice".into()), update, false)?;
println!("Updated {} documents", count);
// Delete
let count = collection.delete_one(Query::new().eq("name", "Alice".into()))?;
println!("Deleted {} documents", count);
Ok(())
}
```
### Advanced Querying
```rust
use luckdb::{Query, Value};
use serde_json::json;
fn main() -> luckdb::Result<()> {
let mut client = Client::new();
let db = client.db("test");
let collection = db.collection("users");
// Insert some test data
collection.insert(json!({"name": "Alice", "age": 30, "city": "New York", "active": true}))?;
collection.insert(json!({"name": "Bob", "age": 25, "city": "San Francisco", "active": false}))?;
collection.insert(json!({"name": "Charlie", "age": 35, "city": "New York", "active": true}))?;
collection.insert(json!({"name": "David", "age": 40, "city": "Chicago", "active": true}))?;
// Find active users in New York
let query = Query::new()
.eq("city", "New York".into())
.eq("active", Value::Bool(true));
let results = collection.find(query, None)?;
println!("Active users in New York:");
for (id, doc) in results {
println!(" {}: {}", id, doc);
}
// Find users older than 30 or inactive users
let query1 = Query::new().gt("age", json!(30));
let query2 = Query::new().eq("active", Value::Bool(false));
let query = Query::new().or(vec![query1, query2]);
let results = collection.find(query, None)?;
println!("Users older than 30 or inactive:");
for (id, doc) in results {
println!(" {}: {}", id, doc);
}
// Find users with specific interests
collection.insert(json!({
"name": "Eve",
"age": 28,
"city": "Boston",
"interests": ["reading", "coding", "hiking"]
}))?;
let query = Query::new().all("interests", vec!["reading".into(), "hiking".into()]);
let results = collection.find(query, None)?;
println!("Users with both reading and hiking interests:");
for (id, doc) in results {
println!(" {}: {}", id, doc);
}
Ok(())
}
```
### Indexing
```rust
use luckdb::{Client, Index, IndexType, Query};
use serde_json::json;
fn main() -> luckdb::Result<()> {
let mut client = Client::new();
let db = client.db("test");
let collection = db.collection("users");
// Insert test data
for i in 0..1000 {
collection.insert(json!({
"name": format!("User {}", i),
"age": i % 50 + 20,
"city": ["New York", "San Francisco", "Chicago", "Boston"][i % 4],
"active": i % 3 != 0
}))?;
}
// Create indexes
let name_index = Index::new("name_index".to_string(), vec![("name".to_string(), IndexType::Ascending)]);
collection.create_index(name_index)?;
let city_age_index = Index::new("city_age_index".to_string(), vec![
("city".to_string(), IndexType::Ascending),
("age".to_string(), IndexType::Descending)
]);
collection.create_index(city_age_index)?;
// Query using indexes
let query = Query::new().eq("city", "New York".into()).gt("age", json!(30));
let results = collection.find(query, None)?;
println!("Found {} users in New York older than 30", results.len());
// List indexes
let indexes = collection.list_indexes()?;
println!("Indexes:");
for index in indexes {
println!(" {}: {:?}", index.name, index.key);
}
Ok(())
}
```
### Aggregation Pipeline
```rust
use luckdb::{Client, AggregationStage, GroupOperation, GroupId, SortOrder, Query};
use serde_json::json;
fn main() -> luckdb::Result<()> {
let mut client = Client::new();
let db = client.db("test");
let collection = db.collection("sales");
// Insert test data
collection.insert(json!({
"product": "Laptop",
"category": "Electronics",
"price": 1200,
"quantity": 1,
"date": "2023-01-15",
"customer": "Alice"
}))?;
collection.insert(json!({
"product": "Phone",
"category": "Electronics",
"price": 800,
"quantity": 2,
"date": "2023-01-16",
"customer": "Bob"
}))?;
collection.insert(json!({
"product": "Desk Chair",
"category": "Furniture",
"price": 200,
"quantity": 1,
"date": "2023-01-17",
"customer": "Alice"
}))?;
collection.insert(json!({
"product": "Monitor",
"category": "Electronics",
"price": 300,
"quantity": 1,
"date": "2023-01-18",
"customer": "Charlie"
}))?;
// Calculate total sales by category
let pipeline = vec![
AggregationStage::Group(GroupSpecification {
id: GroupId::Field("category".to_string()),
operations: {
let mut ops = std::collections::HashMap::new();
ops.insert("total_sales".to_string(),
GroupOperation::Sum(json!({ "$multiply": ["$price", "$quantity"] })));
ops.insert("count".to_string(), GroupOperation::Sum(json!(1)));
ops
},
}),
AggregationStage::Sort(vec![("total_sales".to_string(), SortOrder::Descending)])
];
let results = collection.aggregate(pipeline)?;
println!("Sales by category:");
for doc in results {
println!(" {}", doc);
}
// Find top customers
let pipeline = vec![
AggregationStage::Group(GroupSpecification {
id: GroupId::Field("customer".to_string()),
operations: {
let mut ops = std::collections::HashMap::new();
ops.insert("total_spent".to_string(),
GroupOperation::Sum(json!({ "$multiply": ["$price", "$quantity"] })));
ops.insert("purchase_count".to_string(), GroupOperation::Sum(json!(1)));
ops
},
}),
AggregationStage::Sort(vec![("total_spent".to_string(), SortOrder::Descending)]),
AggregationStage::Limit(3)
];
let results = collection.aggregate(pipeline)?;
println!("Top customers:");
for doc in results {
println!(" {}", doc);
}
Ok(())
}
```
### Persistence
```rust
use luckdb::{Client, config::DatabaseConfig};
use serde_json::json;
fn main() -> luckdb::Result<()> {
// Create a client with encrypted storage
let config = DatabaseConfig::with_storage_path("./data")
.with_encryption("secure_password");
let mut client = Client::with_config(config)?;
// Load existing data if any (will decrypt automatically)
client.load()?;
// Get or create database and collection
let db = client.db("myapp");
let collection = db.collection("users");
// Insert a document
let doc = json!({
"name": "Alice",
"email": "alice@example.com",
"created_at": "2023-01-01T00:00:00Z",
"sensitive_data": "encrypted information"
});
let id = collection.insert(doc)?;
println!("Inserted document with ID: {}", id);
// Save data to disk (will be encrypted automatically)
client.save()?;
println!("Data saved to disk with encryption");
// Later, load the data again
let config2 = DatabaseConfig::with_storage_path("./data")
.with_encryption("secure_password");
let mut client2 = Client::with_config(config2)?;
client2.load()?;
let db2 = client2.db("myapp");
let collection2 = db2.collection("users");
// Query the loaded data (decrypted automatically)
let results = collection2.find(luckdb::Query::new(), None)?;
println!("Loaded {} documents", results.len());
for (id, doc) in results {
println!(" {}: {}", id, doc);
}
Ok(())
}
```
### Configuration File Management
```rust
use luckdb::{Client, config::DatabaseConfig};
fn main() -> luckdb::Result<()> {
// Create configuration
let mut config = DatabaseConfig::new();
config.storage_path = Some("./app_data".into());
config.encryption_enabled = true;
config.encryption_password = Some("app_secret_key".to_string());
config.auth_username = Some("admin".to_string());
config.auth_password = Some("admin_password".to_string());
config.max_backup_files = 10;
// Save configuration to file
config.save_to_file("luckdb_config.toml")?;
println!("Configuration saved to luckdb_config.toml");
// Load configuration from file
let loaded_config = DatabaseConfig::load_from_file("luckdb_config.toml")?;
let mut client = Client::with_config(loaded_config)?;
// Use client with loaded configuration
let db = client.db("production");
println!("Database client ready with configuration");
Ok(())
}
```
### Server CLI Tool
LuckDB includes a comprehensive command-line interface for running production-ready servers with advanced configuration options, logging, and signal handling.
#### Installation
Build the CLI tool as part of the LuckDB project:
```bash
cargo build --release
# The CLI binary will be at:
# Linux/macOS: target/release/luckdb-server-cli
# Windows: target/release/luckdb-server-cli.exe
```
#### Quick Start
```bash
# Start server with default settings
luckdb-server-cli
# Start server with encryption and authentication
luckdb-server-cli --encrypt --password "secure_password" --auth --username admin --auth-password "auth_pass"
# Start server with configuration file
luckdb-server-cli --config config.toml
# Development mode (in-memory, debug logging)
luckdb-server-cli --dev --log-level debug
```
#### Configuration Files
Generate configuration templates for different environments:
```bash
# Create production configuration
luckdb-server-cli config --output prod.toml --config-type prod
# Create development configuration
luckdb-server-cli config --output dev.toml --config-type dev
# Create test configuration
luckdb-server-cli config --output test.toml --config-type test
```
#### CLI Options
| `--config` | Path to TOML configuration file | `--config config.toml` |
| `--address` | Server bind address | `--address 127.0.0.1` |
| `--port` | Server port | `--port 27017` |
| `--storage` | Storage path for database files | `--storage ./data` |
| `--encrypt` | Enable AES-256 encryption | `--encrypt` |
| `--password` | Encryption password | `--password "secure_pass"` |
| `--auth` | Enable authentication | `--auth` |
| `--username` | Authentication username | `--username admin` |
| `--auth-password` | Authentication password | `--auth-password "auth_pass"` |
| `--log-level` | Log level (error, warn, info, debug, trace) | `--log-level info` |
| `--log-file` | Log file path | `--log-file server.log` |
| `--dev` | Development mode | `--dev` |
| `--auto-save` | Auto-save interval in seconds | `--auto-save 300` |
#### Production Deployment
For production deployments, use a configuration file:
```bash
# 1. Create production configuration
luckdb-server-cli config --output /etc/luckdb/config.toml --config-type prod
# 2. Edit configuration with secure passwords
vim /etc/luckdb/config.toml
# 3. Set appropriate file permissions
chmod 600 /etc/luckdb/config.toml
# 4. Start server
luckdb-server-cli --config /etc/luckdb/config.toml
```
#### Configuration File Format
```toml
[luckdb]
# Production storage path
storage_path = "/var/lib/luckdb/data"
# Always enable encryption in production
encryption_enabled = true
encryption_password = "CHANGE_ME_IN_PRODUCTION"
# Authentication
auth_username = "admin"
auth_password = "CHANGE_ME_IN_PRODUCTION"
# Server address
server_address = "127.0.0.1:27017"
# Auto-save every 5 minutes
auto_save_interval_seconds = 300
# Maximum backup files
max_backup_files = 30
# Logging
log_level = "info"
log_file = "/var/log/luckdb/server.log"
```
#### Environment Variables
All configuration options can be overridden with environment variables:
```bash
export LUCKDB_ENCRYPTION_PASSWORD="secure_password"
export LUCKDB_LOG_LEVEL="debug"
luckdb-server-cli
```
#### Signal Handling
The CLI tool supports graceful shutdown:
- **Ctrl+C**: Graceful shutdown
- **Double Ctrl+C**: Force shutdown
#### Features
- **Comprehensive Configuration**: TOML files, command-line arguments, and environment variables
- **Security**: AES-256 encryption, authentication, and secure defaults
- **Production Ready**: Structured logging, signal handling, and performance options
- **Multi-Environment Support**: Development, testing, and production configurations
- **Validation**: Configuration validation with helpful error messages
For detailed documentation, see [`CLI_README.md`](CLI_README.md).
### Advanced Encryption
```rust
use luckdb::encryption::{AesEncryption, EncryptionKey};
fn main() -> luckdb::Result<()> {
// Create encryption key from password
let key = EncryptionKey::from_password("my_secure_password", b"unique_salt_2024");
// Encrypt sensitive data
let sensitive_data = r#"{
"api_key": "sk-1234567890abcdef",
"database_password": "db_secret_2024",
"private_key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
}"#;
let encrypted_data = AesEncryption::encrypt_string(sensitive_data, &key)?;
println!("Encrypted data: {}", encrypted_data);
// Decrypt data
let decrypted_data = AesEncryption::decrypt_string(&encrypted_data, &key)?;
println!("Decrypted data: {}", decrypted_data);
// Verify integrity
assert_eq!(sensitive_data, decrypted_data);
println!("Encryption/decryption successful and integrity verified");
Ok(())
}
```
### Client-Server Mode
```rust
use luckdb::{Server, config::DatabaseConfig};
use std::net::SocketAddr;
fn main() -> luckdb::Result<()> {
// Start the server in a separate thread with configuration
let server_thread = std::thread::spawn(|| {
let server_config = DatabaseConfig::with_storage_path("./server_data")
.with_auth("admin", "password123")
.with_encryption("server_secret");
let addr: SocketAddr = "127.0.0.1:27017".parse().unwrap();
let mut server = Server::with_config(addr, server_config).unwrap();
if let Err(e) = server.start() {
eprintln!("Server error: {}", e);
}
});
// Give the server time to start
std::thread::sleep(std::time::Duration::from_millis(500));
// Connect to the server (using a TCP client would go here)
// Note: RemoteClient implementation would be needed for full client-server functionality
println!("Server started with authentication and encryption");
println!("Connect to luckdb://127.0.0.1:27017");
println!("Username: admin, Password: password123");
// For demonstration, we'll use a local client instead
use luckdb::Client;
let mut local_client = Client::with_storage_path("luckdb://localhost", "./local_data")
.with_encryption("local_secret")?;
let db = local_client.db("testdb");
let collection = db.collection("local_users");
use serde_json::json;
let doc = json!({"name": "Local User", "type": "client"});
let id = collection.insert(doc)?;
local_client.save()?;
println!("Local client inserted document: {}", id);
server_thread.join().unwrap();
Ok(())
}
```
Note: This is just a basic example to give you an idea of how to use LuckDB. In a real-world application, you may want to add more error handling and security features.
```rust
#![allow(warnings)]
use luckdb::{Client, Server};
use std::net::SocketAddr;
use std::path::PathBuf;
fn main() -> luckdb::Result<()> {
// Start the server in a separate thread
let server_thread = std::thread::spawn(|| {
// Create server with authentication
let addr: SocketAddr = "127.0.0.1:27017".parse().unwrap();
let storage_path = Some(PathBuf::from("./data"));
let mut server = Server::new(addr, storage_path)
.with_auth("admin".to_string(), "password123".to_string());
server.start().unwrap();
});
// Give the server time to start
std::thread::sleep(std::time::Duration::from_millis(100));
// Connect to the server
let remote_client = luckdb::RemoteClient::new("127.0.0.1:27017".parse().unwrap());
let mut connection = remote_client.connect()?;
let response = connection.send_command("AUTH admin password123")?;
println!("Authentication response: {}", response);
// Send commands
let response = connection.send_command("INSERT mydb users {\"name\":\"Alice\",\"age\":30}")?;
println!("Server response: {}", response);
let response = connection.send_command("FIND mydb users {\"name\":\"Alice\"}")?;
println!("Server response: {}", response);
let response = connection.send_command("SAVE")?;
println!("Server response: {}", response);
// Close the connection
// connection.close()?;
// Stop the server
let response = connection.send_command("EXIT")?;
println!("Server response: {}", response);
server_thread.join().unwrap();
Ok(())
}
```
## License
LuckDB is licensed under the MIT License. See [LICENSE](LICENSE) for details.