IC-NoSQL
A type-safe NoSQL database library for Internet Computer canisters with automatic memory management.
Overview
IC-NoSQL provides a high-level interface for storing and querying structured data in Internet Computer canisters. It handles memory allocation, serialization, and provides type-safe operations with pagination support.
Features
- Type Safety: Compile-time type checking for all database operations
- Automatic Memory Management: Handles memory allocation with conflict prevention
- Multiple Model Support: Store different data types in the same canister
- Pagination: Built-in pagination for efficient querying
- Data Persistence: Data survives canister upgrades
- CRUD Operations: Complete Create, Read, Update, Delete support
Quick Start
1. Define Your Models
use ;
use ;
define_model!
define_model!
2. Initialize the Database
use DatabaseManager;
thread_local!
3. Database Operations
// Create
let user = User ;
DB_MANAGER.with?;
// Read
let user = DB_MANAGER.with?;
// Query with pagination
let response = DB_MANAGER.with?;
// Update
let mut updated_user = user.clone;
updated_user.email = "newemail@example.com".to_string;
DB_MANAGER.with?;
// Delete
DB_MANAGER.with?;
API Reference
DatabaseManager
Registration
register_model(name: &str, memory_id: Option<u8>, max_size: Option<u32>) -> Result<()>
CRUD Operations
insert<T: Model>(collection: &str, id: &str, data: &T) -> Result<()>get<T: Model>(collection: &str, id: &str) -> Result<Option<T>>update<T: Model>(collection: &str, id: &str, data: &T) -> Result<()>delete(collection: &str, id: &str) -> Result<()>
Querying
query<T: Model>(collection: &str, limit: usize, page: usize) -> Result<QueryResponse<T>>stats() -> Vec<String>
Model Trait
Use the define_model! macro to automatically implement the Model trait:
define_model!
// Or for simple models without secondary keys:
define_model!
Memory Layout
IC-NoSQL uses stable memory with automatic allocation:
- Each model gets a unique memory ID (0-255)
- Automatic conflict detection and prevention
Testing
The package includes comprehensive tests and stress tests:
# Build example canister
# Run stress tests (requires PocketIC)
Example Canister
See example-canister/ for a complete implementation demonstrating:
- User management system
- Blog posts with comments
- Pagination and CRUD operations
- Canister upgrade persistence
Available Endpoints
User Management
create_user(username: text, email: text) -> Result<User, text>get_user(id: text) -> Result<User, text>list_users(page: nat, size: nat) -> Result<vec User, text>
Post Management
create_post(user_id: text, title: text, content: text) -> Result<Post, text>get_post(id: text) -> Result<Post, text>list_posts(page: nat, size: nat) -> Result<vec Post, text>
Comment Management
create_comment(post_id: text, user_id: text, content: text) -> Result<Comment, text>get_comment(id: text) -> Result<Comment, text>list_comments(page: nat, size: nat) -> Result<vec Comment, text>
Best Practices
- Memory Management: Always register models during
init()orpost_upgrade() - ID Generation: Use proper ID generation strategies (UUIDs, timestamps, etc.)
- Error Handling: Always handle database errors appropriately
- Pagination: Use pagination for large result sets to avoid query limits
- Validation: Validate data before storing (e.g., check if referenced entities exist)
- Upgrades: Re-register all models after canister upgrades
Troubleshooting
Common Issues
- Memory Conflicts: Ensure unique memory IDs for different models
- Upgrade Issues: Always re-register models after upgrades
- Serialization Errors: Ensure all fields are properly serializable
- Memory Limits: Consider pagination for large datasets
Debug Tips
- Use
ic_cdk::println!for debugging in canisters - Check memory usage with
stats()method - Verify model registration before operations
- Test upgrades in development environment
Development
Running Tests on NixOS
If using NixOS with the IC development environment:
# Start nix-shell with IC tools
# Set PocketIC binary path
# Run tests