410 lines
9.0 KiB
Markdown
410 lines
9.0 KiB
Markdown
|
|
# Persistence Layer Quick Start Guide
|
||
|
|
|
||
|
|
This guide shows how to use the new trait-based database abstraction layer.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Using SQLite Backend
|
||
|
|
|
||
|
|
### 1. Create Database Instance
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use workspace_core::SqliteDatabase;
|
||
|
|
|
||
|
|
#[tokio::main]
|
||
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||
|
|
// Create SQLite database
|
||
|
|
let db = SqliteDatabase::new("workspace.db").await?;
|
||
|
|
|
||
|
|
// Or use in-memory database (for testing)
|
||
|
|
let db = SqliteDatabase::new_memory().await?;
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Create a Project
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use workspace_core::{SqliteDatabase, DbProject};
|
||
|
|
use chrono::Utc;
|
||
|
|
use uuid::Uuid;
|
||
|
|
|
||
|
|
let db = SqliteDatabase::new("workspace.db").await?;
|
||
|
|
|
||
|
|
let project = DbProject {
|
||
|
|
id: Uuid::new_v4().to_string(),
|
||
|
|
name: "My Project".to_string(),
|
||
|
|
version: "0.1.0".to_string(),
|
||
|
|
description: "A new project".to_string(),
|
||
|
|
project_type: "MultiLang".to_string(),
|
||
|
|
current_phase: "Creation".to_string(),
|
||
|
|
created_at: Utc::now().to_rfc3339(),
|
||
|
|
updated_at: Utc::now().to_rfc3339(),
|
||
|
|
};
|
||
|
|
|
||
|
|
db.create_project(&project).await?;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Retrieve Projects
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Get all projects
|
||
|
|
let projects = db.list_projects().await?;
|
||
|
|
println!("Found {} projects", projects.len());
|
||
|
|
|
||
|
|
// Get specific project
|
||
|
|
if let Some(project) = db.get_project("project-id").await? {
|
||
|
|
println!("Project: {}", project.name);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Add Checklist Items
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use workspace_core::DbChecklistItem;
|
||
|
|
|
||
|
|
let item = DbChecklistItem {
|
||
|
|
id: Uuid::new_v4().to_string(),
|
||
|
|
project_id: project.id.clone(),
|
||
|
|
phase: "Creation".to_string(),
|
||
|
|
task_id: "task-1".to_string(),
|
||
|
|
description: "Complete project setup".to_string(),
|
||
|
|
completed: false,
|
||
|
|
completed_at: None,
|
||
|
|
created_at: Utc::now().to_rfc3339(),
|
||
|
|
task_type: "Setup".to_string(),
|
||
|
|
task_priority: "High".to_string(),
|
||
|
|
task_due: Some("2025-12-31".to_string()),
|
||
|
|
task_estimation: Some("2d".to_string()),
|
||
|
|
task_deps: "[]".to_string(),
|
||
|
|
task_note: Some("Remember to test").to_string(),
|
||
|
|
task_name: Some("Setup".to_string()),
|
||
|
|
};
|
||
|
|
|
||
|
|
db.create_checklist_item(&item).await?;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5. Track Phase Transitions
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use workspace_core::DbPhaseTransition;
|
||
|
|
|
||
|
|
let transition = DbPhaseTransition {
|
||
|
|
id: Uuid::new_v4().to_string(),
|
||
|
|
project_id: project.id.clone(),
|
||
|
|
from_phase: "Creation".to_string(),
|
||
|
|
to_phase: "Development".to_string(),
|
||
|
|
timestamp: Utc::now().to_rfc3339(),
|
||
|
|
reason: Some("Project setup complete".to_string()),
|
||
|
|
};
|
||
|
|
|
||
|
|
db.record_phase_transition(&transition).await?;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Using Configuration Files
|
||
|
|
|
||
|
|
### 1. Create Configuration File
|
||
|
|
|
||
|
|
**`configs/database.toml`**
|
||
|
|
```toml
|
||
|
|
engine = "sqlite"
|
||
|
|
|
||
|
|
[sqlite]
|
||
|
|
path = "~/.local/share/core/workspace.db"
|
||
|
|
max_connections = 5
|
||
|
|
timeout_secs = 30
|
||
|
|
wal_mode = true
|
||
|
|
pragma_synchronous = "NORMAL"
|
||
|
|
pragma_cache_size = 2000
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Load Configuration
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use workspace_core::DatabaseConfig;
|
||
|
|
use std::path::Path;
|
||
|
|
|
||
|
|
let config = DatabaseConfig::load_from_file("configs/database.toml")?;
|
||
|
|
|
||
|
|
// Validate configuration
|
||
|
|
config.validate()?;
|
||
|
|
|
||
|
|
// Check which backend is configured
|
||
|
|
if config.is_sqlite() {
|
||
|
|
println!("Using SQLite backend");
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Runtime Selection (Future)
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use workspace_core::{DatabaseConfig, Database, SqliteDatabase};
|
||
|
|
|
||
|
|
let config = DatabaseConfig::load_from_file("configs/database.toml")?;
|
||
|
|
|
||
|
|
// Create appropriate backend based on configuration
|
||
|
|
let db: Box<dyn Database> = match config.engine.as_str() {
|
||
|
|
"sqlite" => {
|
||
|
|
let sqlite_config = config.sqlite.ok_or("No SQLite config")?;
|
||
|
|
let db = SqliteDatabase::new(&sqlite_config.path).await?;
|
||
|
|
Box::new(db)
|
||
|
|
}
|
||
|
|
"surrealdb" => {
|
||
|
|
// Once SurrealDB is implemented:
|
||
|
|
// let db = SurrealDatabase::new(...).await?;
|
||
|
|
// Box::new(db)
|
||
|
|
return Err("SurrealDB not yet implemented".into());
|
||
|
|
}
|
||
|
|
_ => return Err("Unknown database engine".into()),
|
||
|
|
};
|
||
|
|
|
||
|
|
// Use database transparently
|
||
|
|
db.list_projects().await?;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Error Handling
|
||
|
|
|
||
|
|
### Pattern 1: Propagate with `?`
|
||
|
|
|
||
|
|
```rust
|
||
|
|
async fn create_project(db: &dyn Database, name: &str) -> Result<()> {
|
||
|
|
let project = DbProject {
|
||
|
|
id: Uuid::new_v4().to_string(),
|
||
|
|
name: name.to_string(),
|
||
|
|
// ... other fields ...
|
||
|
|
};
|
||
|
|
|
||
|
|
db.create_project(&project).await?; // Propagates error
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Pattern 2: Match Errors
|
||
|
|
|
||
|
|
```rust
|
||
|
|
match db.get_project("nonexistent").await {
|
||
|
|
Ok(Some(project)) => println!("Found: {}", project.name),
|
||
|
|
Ok(None) => println!("Project not found"),
|
||
|
|
Err(e) => eprintln!("Error: {}", e),
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Pattern 3: Check Error Type
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use workspace_core::error::LifecycleError;
|
||
|
|
|
||
|
|
match db.create_project(&project).await {
|
||
|
|
Ok(p) => println!("Created: {}", p.id),
|
||
|
|
Err(LifecycleError::Database(msg)) => eprintln!("DB error: {}", msg),
|
||
|
|
Err(e) => eprintln!("Other error: {}", e),
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
### In-Memory Database
|
||
|
|
|
||
|
|
```rust
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_project_creation() {
|
||
|
|
let db = SqliteDatabase::new_memory().await.unwrap();
|
||
|
|
|
||
|
|
let project = DbProject {
|
||
|
|
id: "test-1".to_string(),
|
||
|
|
name: "Test".to_string(),
|
||
|
|
// ... other fields ...
|
||
|
|
};
|
||
|
|
|
||
|
|
let created = db.create_project(&project).await.unwrap();
|
||
|
|
assert_eq!(created.name, "Test");
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Temporary File Database
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use tempfile::tempdir;
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_persistence() {
|
||
|
|
let dir = tempdir().unwrap();
|
||
|
|
let db_path = dir.path().join("test.db").to_string_lossy().to_string();
|
||
|
|
|
||
|
|
let db = SqliteDatabase::new(&db_path).await.unwrap();
|
||
|
|
|
||
|
|
// Your test code here
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
### 1. Use Type Alias for Result
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use workspace_core::error::Result;
|
||
|
|
|
||
|
|
async fn my_function() -> Result<String> {
|
||
|
|
// Function body
|
||
|
|
Ok("success".to_string())
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Always Validate IDs
|
||
|
|
|
||
|
|
```rust
|
||
|
|
if let Some(project) = db.get_project(&project_id).await? {
|
||
|
|
// Process project
|
||
|
|
} else {
|
||
|
|
eprintln!("Project {} not found", project_id);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Batch Operations When Possible
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Good: create multiple items in a loop
|
||
|
|
for item in items {
|
||
|
|
db.create_checklist_item(&item).await?;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Use Configuration for Paths
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Good
|
||
|
|
let path = config.sqlite.unwrap().path;
|
||
|
|
let db = SqliteDatabase::new(&path).await?;
|
||
|
|
|
||
|
|
// Bad
|
||
|
|
let db = SqliteDatabase::new("workspace.db").await?; // hardcoded
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5. Always Handle Connection Errors
|
||
|
|
|
||
|
|
```rust
|
||
|
|
match db.ping().await {
|
||
|
|
Ok(()) => println!("Database is healthy"),
|
||
|
|
Err(e) => eprintln!("Database is unavailable: {}", e),
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Common Operations
|
||
|
|
|
||
|
|
### List All Projects for a Phase
|
||
|
|
|
||
|
|
```rust
|
||
|
|
let projects = db.list_projects().await?;
|
||
|
|
for project in projects {
|
||
|
|
if project.current_phase == "Development" {
|
||
|
|
println!("{}: {}", project.id, project.name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Get Project Completion Percentage
|
||
|
|
|
||
|
|
```rust
|
||
|
|
let completion = db.get_completion_percentage(&project_id, "Development").await?;
|
||
|
|
println!("Phase completion: {:.1}%", completion);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Record Security Assessment
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use workspace_core::DbSecurityAssessment;
|
||
|
|
|
||
|
|
let assessment = DbSecurityAssessment {
|
||
|
|
id: Uuid::new_v4().to_string(),
|
||
|
|
project_id: project.id.clone(),
|
||
|
|
profile: "OWASP".to_string(),
|
||
|
|
risk_level: "Medium".to_string(),
|
||
|
|
passed_rules: 45,
|
||
|
|
failed_rules: 5,
|
||
|
|
critical_issues: 0,
|
||
|
|
assessment_date: Utc::now().to_rfc3339(),
|
||
|
|
};
|
||
|
|
|
||
|
|
db.create_security_assessment(&assessment).await?;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Get Phase History
|
||
|
|
|
||
|
|
```rust
|
||
|
|
let history = db.get_phase_history(&project_id).await?;
|
||
|
|
for transition in history {
|
||
|
|
println!(
|
||
|
|
"{}: {} → {} (reason: {:?})",
|
||
|
|
transition.timestamp,
|
||
|
|
transition.from_phase,
|
||
|
|
transition.to_phase,
|
||
|
|
transition.reason
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### Issue: "Database is locked"
|
||
|
|
**Solution**: SQLite doesn't handle high concurrency well. For production:
|
||
|
|
1. Reduce connection pool size
|
||
|
|
2. Use timeout configurations
|
||
|
|
3. Switch to SurrealDB (when available)
|
||
|
|
|
||
|
|
### Issue: "File not found"
|
||
|
|
**Solution**: Ensure directory exists:
|
||
|
|
```rust
|
||
|
|
let path = Path::new("~/.local/share/core/workspace.db");
|
||
|
|
if let Some(parent) = path.parent() {
|
||
|
|
std::fs::create_dir_all(parent)?;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Issue: "Connection timeout"
|
||
|
|
**Solution**: Increase timeout in configuration:
|
||
|
|
```toml
|
||
|
|
[sqlite]
|
||
|
|
timeout_secs = 60 # Increase from default 30
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Next Steps
|
||
|
|
|
||
|
|
1. **Review** `SURREALDB_IMPLEMENTATION_PROGRESS.md` for architecture details
|
||
|
|
2. **Check** `src/persistence/mod.rs` for full Database trait documentation
|
||
|
|
3. **Explore** `src/persistence/config.rs` for configuration options
|
||
|
|
4. **Study** `src/persistence/sqlite_impl.rs` for implementation patterns
|
||
|
|
5. **Run** `cargo test -p syntaxis-core --lib persistence` to verify setup
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- [Database Trait Documentation](src/persistence/mod.rs)
|
||
|
|
- [Configuration Guide](src/persistence/config.rs)
|
||
|
|
- [SQLite Implementation](src/persistence/sqlite_impl.rs)
|
||
|
|
- [Error Types](src/persistence/error.rs)
|
||
|
|
- [Migration Infrastructure](src/persistence/migration.rs)
|
||
|
|
- [Implementation Progress](SURREALDB_IMPLEMENTATION_PROGRESS.md)
|
||
|
|
- [Original Plan](SURREALDB_IMPLEMENTATION_FINAL_PLAN.md)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Last Updated**: November 15, 2025
|
||
|
|
**Status**: Ready for Use
|
||
|
|
**Support**: See SURREALDB_IMPLEMENTATION_PROGRESS.md for more details
|