# SQLite vs SurrealDB Comparison for syntaxis ## Side-by-Side Feature Comparison | Feature | SQLite | SurrealDB | Advantage | |---------|--------|-----------|-----------| | **Type** | Relational SQL | Multi-model (Document + Graph + SQL) | SurrealDB (more flexible) | | **Schema** | Fixed tables | Flexible/Dynamic | SurrealDB (domain-driven) | | **Relationships** | Foreign Keys (joins) | Native Graph (edges) | SurrealDB (phase transitions) | | **Data Format** | Row/Column | JSON Documents | SurrealDB (JSON-native) | | **Real-time** | Polling only | WebSocket subscriptions | SurrealDB (live updates) | | **Scalability** | Single file (embedded) | Distributed | SurrealDB (cloud-ready) | | **Query Language** | SQL | SurrealQL (SQL-like + graph) | SurrealDB (richer) | | **ACID** | ✅ Yes | ✅ Yes | Same | | **Async Driver** | ✅ sqlx | ✅ Native | Same | | **Versioning** | Manual (audit tables) | Built-in time-travel | SurrealDB | | **Transactions** | ✅ Yes | ✅ Yes | Same | | **JSON Support** | TEXT/JSON1 | Native | SurrealDB | | **Setup Complexity** | Simple (file-based) | Medium (standalone server) | SQLite (easier) | | **Operator Cost** | Low | Medium | SQLite | --- ## Query Comparison: Real-world syntaxis Scenarios ### Scenario 1: Get All Completed Tasks for a Phase **SQLite:** ```sql SELECT ci.*, p.name as project_name FROM checklist_items ci JOIN projects p ON ci.project_id = p.id WHERE ci.phase = 'Development' AND ci.completed = true AND p.archived = false ORDER BY ci.completed_at DESC LIMIT 50; ``` **SurrealDB:** ```surql SELECT id, task_id, description, completed_at, (->checklist_items<-projects).name as project_name FROM checklist_items WHERE phase = 'Development' AND completed = true AND !projects<-checklist_items<-projects[archived = true] ORDER BY completed_at DESC LIMIT 50; ``` **Verdict:** SQLite simpler, SurrealDB more flexible --- ### Scenario 2: Find All Phase Transitions for a Project (Graph Query) **SQLite:** ```sql -- Manual path query with JOINs SELECT pt.*, p1.name as from_phase, p2.name as to_phase FROM phase_transitions pt LEFT JOIN phases p1 ON p1.id = pt.from_phase LEFT JOIN phases p2 ON p2.id = pt.to_phase WHERE pt.project_id = ? ORDER BY pt.timestamp DESC; -- Alternative: serialize in code and parse SELECT * FROM phase_transitions WHERE project_id = ? ORDER BY timestamp DESC; -- Then manually traverse ``` **SurrealDB:** ```surql -- Query graph edges directly SELECT * FROM project:123 -> transitioned_to -> * ORDER BY time DESC; -- Or with details SELECT id, type::string(->transitioned_to) as to_phase, reason, timestamp FROM project:123 -> transitioned_to; -- Or get full path (recursive) SELECT <- transitioned_to <- * FROM project:123; ``` **Verdict:** 🏆 **SurrealDB wins** - Natural graph traversal, no JOINs needed --- ### Scenario 3: Tool Configuration with Dynamic Fields **SQLite:** ```sql -- Store as JSON string CREATE TABLE tool_configurations ( id TEXT PRIMARY KEY, project_id TEXT, tool_name TEXT, config_json TEXT, -- {"rust_version": "1.75", "clippy_level": "pedantic", ...} ... ); -- Retrieve and parse in Rust let result = sqlx::query!( "SELECT config_json FROM tool_configurations WHERE id = ?", id ).fetch_one(&pool).await?; let config: serde_json::Value = serde_json::from_str(&result.config_json)?; let rust_version = config["rust_version"].as_str(); ``` **SurrealDB:** ```surql -- Native document storage CREATE toolconfig:cargo-clippy SET tool_name = 'cargo-clippy', enabled = true, config = { rust_version: '1.75', clippy_level: 'pedantic', lint_rules: ['all', 'pedantic'], exclude_rules: ['unsafe-code'], }, updated_at = now(); -- Query fields directly SELECT config.rust_version FROM toolconfig:cargo-clippy; -- Update nested fields UPDATE toolconfig:cargo-clippy SET config.clippy_level = 'strict' config.updated_at = now(); ``` **Verdict:** 🏆 **SurrealDB wins** - Type-safe nested documents, no string parsing --- ### Scenario 4: Audit Trail - "Show me what changed" **SQLite:** ```sql -- Need separate activity_logs table CREATE TABLE activity_logs ( id TEXT PRIMARY KEY, project_id TEXT, table_name TEXT, action TEXT, -- 'INSERT', 'UPDATE', 'DELETE' before_data TEXT, after_data TEXT, timestamp TEXT, user_id TEXT ); -- Query changes SELECT * FROM activity_logs WHERE project_id = ? AND table_name = 'projects' AND timestamp BETWEEN ? AND ? ORDER BY timestamp DESC; -- Manually compare before/after let before = serde_json::from_str(&result.before_data)?; let after = serde_json::from_str(&result.after_data)?; let diff = compute_diff(before, after); ``` **SurrealDB:** ```surql -- Built-in version control -- Query project state at specific time SELECT * FROM projects VERSION AT ; -- See what changed between times SELECT * FROM projects DIFF FROM TO ; -- See all versions SELECT * FROM projects VERSIONS; -- Automatic - no manual audit tables needed! ``` **Verdict:** 🏆 **SurrealDB wins** - Built-in versioning, no manual tracking --- ### Scenario 5: Real-time Dashboard Updates **SQLite:** ```rust // Polling approach (bad for real-time) let mut interval = tokio::time::interval(Duration::from_secs(5)); loop { interval.tick().await; let projects = sqlx::query_as::<_, Project>( "SELECT * FROM projects WHERE updated_at > ?" ) .bind(&last_update) .fetch_all(&pool) .await?; // Send to dashboard... send_to_dashboard(projects).await; last_update = Utc::now(); } ``` **SurrealDB:** ```rust // WebSocket subscription (real-time push) let mut stream = db.query("LIVE SELECT * FROM projects") .await? .take(0)?; // Automatically gets PUSH notifications for ANY change while let Some(change) = stream.next().await { match change { Ok(notification) => { // Instant notification of change send_to_dashboard(notification).await; } Err(e) => eprintln!("Subscription error: {}", e), } } ``` **Verdict:** 🏆 **SurrealDB wins** - Built-in WebSocket subscriptions, real-time push vs polling --- ### Scenario 6: Complex Filter - Uncompleted Tasks in Active Projects **SQLite:** ```sql SELECT ci.* FROM checklist_items ci JOIN projects p ON ci.project_id = p.id WHERE ci.completed = false AND p.archived = false AND p.current_phase IN ('Development', 'Testing') AND ci.phase = p.current_phase ORDER BY ci.task_priority DESC, CASE WHEN ci.task_due IS NULL THEN 1 ELSE 0 END, ci.task_due ASC; ``` **SurrealDB:** ```surql SELECT * FROM checklist_items WHERE completed = false AND (->projects<-checklist_items).archived = false AND (->projects<-checklist_items).current_phase = phase AND (->projects<-checklist_items).current_phase IN ['Development', 'Testing'] ORDER BY task_priority DESC, task_due ASC NULLS LAST; ``` **Verdict:** Roughly equivalent, SurrealDB graph relations slightly more natural --- ## Performance Comparison ### Read Performance (Queries) ``` Simple Select Complex Join Graph Traversal SQLite ✅✅✅ Very Fast ✅✅ Good ⚠️ Slow (many JOINs) SurrealDB ✅✅ Fast ✅✅ Good ✅✅✅ Excellent ``` ### Write Performance (Inserts) ``` Single Insert Batch Insert Transaction SQLite ✅✅✅ Very Fast ✅✅ Good ✅✅ Good SurrealDB ✅✅ Good ✅✅ Good ✅✅ Good ``` ### Real-time Updates ``` Polling Push Notification SQLite ⚠️ Expensive ✗ Not possible SurrealDB ✗ Not needed ✅✅✅ Excellent ``` --- ## Cost Analysis ### Operational Complexity **SQLite:** - ✅ File-based, no server to run - ✅ Zero configuration - ✅ Single process - ✅ Backup = copy file **SurrealDB:** - ⚠️ Standalone server process - ⚠️ Minimal configuration needed - ⚠️ Network communication (WebSocket/HTTP) - ✅ Cloud-native deployment ### Learning Curve **SQLite:** - ✅ Standard SQL - ✅ Familiar to most developers - ✅ Excellent documentation **SurrealDB:** - ⚠️ New query language (SurrealQL) - ⚠️ Graph concepts (edges, relations) - ⚠️ Fewer examples available ### Migration Effort **Switching from SQLite to SurrealDB:** 1. Define Database trait (4-6 hours) 2. Refactor SQLite to adapter (6-8 hours) 3. Implement SurrealDB adapter (12-16 hours) 4. Data migration (4-6 hours) 5. Testing & validation (8-10 hours) 6. **Total: ~40-50 hours** (1 developer-week) --- ## Recommendation Matrix ### Use SQLite if: - ✅ Single-user or small team - ✅ Embedded deployment (CLI tool, local only) - ✅ Minimal operational overhead desired - ✅ Simple relational data model - ✅ Fast MVP/PoC needed - ✅ Budget constrained **Current Status:** ✅ **syntaxis uses SQLite** ### Use SurrealDB if: - ✅ Multi-user collaborative environment - ✅ Complex relationships (phases, tasks, dependencies) - ✅ Real-time updates required - ✅ Distributed/cloud deployment - ✅ Graph queries common - ✅ Audit trail important **Why SurrealDB fits syntaxis:** 1. **Graph-native phases** - Transitions are natural edges 2. **Complex relationships** - Task dependencies, project relationships 3. **Real-time dashboard** - TUI/Web dashboard updates 4. **Flexible schema** - Tool configs, extensible design 5. **Future-proof** - Scales to enterprise --- ## Migration Path ### Current (Recommended) ``` syntaxis ├── persistence.rs (SQLite only) ├── config.toml (engine: "sqlite") └── workspace.db (embedded) ``` ### Phase 1 (Add Trait) ``` syntaxis ├── persistence/ │ ├── mod.rs (Database trait) │ ├── sqlite_impl.rs (current code) │ └── error.rs ├── config.toml (engine: "sqlite") └── workspace.db ``` ### Phase 2 (Add SurrealDB Support) ``` syntaxis ├── persistence/ │ ├── mod.rs (Database trait) │ ├── sqlite_impl.rs │ ├── surrealdb_impl.rs (NEW) │ └── error.rs ├── config.toml (engine: "sqlite" OR "surrealdb") └── workspace.db / surrealdb-server ``` ### Phase 3 (Recommendation for Enterprise) ``` syntaxis ├── persistence/ │ ├── mod.rs (Database trait) │ ├── sqlite_impl.rs (default) │ ├── surrealdb_impl.rs (advanced) │ ├── postgres_impl.rs (future) │ └── error.rs ├── configs/ │ ├── sqlite.toml (embedded) │ ├── surrealdb.toml (cloud-ready) │ └── postgres.toml (enterprise) └── docker-compose.yml (SurrealDB + PostgreSQL) ``` --- ## Final Recommendation | Scenario | Recommendation | Reason | |----------|---|---| | **Development** | SQLite | No setup, fast iteration | | **Single User** | SQLite | No overhead needed | | **Small Team** | SQLite | Still manageable | | **Large Team** | SurrealDB | Real-time, graph queries | | **Cloud Deployment** | SurrealDB | Distributed, scalable | | **Enterprise** | SurrealDB + PostgreSQL | Choice, backup options | **For syntaxis:** - **Keep SQLite as default** (current) - **Add SurrealDB support** in Phase 2 (next quarter) - **Let users choose** via configuration - **No breaking changes** with trait-based design --- ## References - SQLite Docs: https://sqlite.org/ - SurrealDB Docs: https://surrealdb.com/docs - SurrealDB Rust Client: https://github.com/surrealdb/surrealdb.rs - Example Code: See `SURREALDB_EXAMPLE.rs` in this directory