- SQL store with SQLite-compatible subset (sqlparser 0.43) - CREATE TABLE, INSERT, SELECT, UPDATE, DELETE - WHERE clauses, ORDER BY, LIMIT - Aggregates (COUNT, SUM, AVG, MIN, MAX) - UNIQUE and NOT NULL constraints - BTreeMap-based indexes - Graph store for relationship-based queries - Nodes with labels and properties - Edges with types and weights - BFS/DFS traversal - Dijkstra shortest path - Cypher-like query parser (MATCH, CREATE, DELETE, SET) - Raft consensus replication for high availability - Leader election with randomized timeouts - Log replication with AppendEntries RPC - Snapshot management for log compaction - Cluster configuration and joint consensus - Full RPC message serialization All 159 tests pass.
505 lines
18 KiB
Markdown
505 lines
18 KiB
Markdown
# Phase 10 Advanced Database Features
|
|
|
|
> Implementation plan for SQL, Graph, and Replication features in Synor Database L2
|
|
|
|
## Overview
|
|
|
|
These advanced features extend the Synor Database to support:
|
|
1. **Relational (SQL)** - SQLite-compatible query subset for structured data
|
|
2. **Graph Store** - Relationship queries for connected data
|
|
3. **Replication** - Raft consensus for high availability
|
|
|
|
## Feature 1: Relational (SQL) Store
|
|
|
|
### Purpose
|
|
Provide a familiar SQL interface for developers who need structured relational queries, joins, and ACID transactions.
|
|
|
|
### Architecture
|
|
|
|
```text
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ SQL QUERY LAYER │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
|
│ │ SQL Parser │ │ Planner │ │ Executor │ │
|
|
│ │ (sqlparser) │ │ (logical) │ │ (physical) │ │
|
|
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ Table Storage Engine │ │
|
|
│ │ - Row-oriented storage │ │
|
|
│ │ - B-tree indexes │ │
|
|
│ │ - Transaction log (WAL) │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Supported SQL Subset
|
|
|
|
| Category | Statements |
|
|
|----------|------------|
|
|
| DDL | CREATE TABLE, DROP TABLE, ALTER TABLE |
|
|
| DML | SELECT, INSERT, UPDATE, DELETE |
|
|
| Clauses | WHERE, ORDER BY, LIMIT, OFFSET, GROUP BY, HAVING |
|
|
| Joins | INNER JOIN, LEFT JOIN, RIGHT JOIN |
|
|
| Functions | COUNT, SUM, AVG, MIN, MAX, COALESCE |
|
|
| Operators | =, !=, <, >, <=, >=, AND, OR, NOT, IN, LIKE |
|
|
|
|
### Data Types
|
|
|
|
| SQL Type | Rust Type | Storage |
|
|
|----------|-----------|---------|
|
|
| INTEGER | i64 | 8 bytes |
|
|
| REAL | f64 | 8 bytes |
|
|
| TEXT | String | Variable |
|
|
| BLOB | Vec<u8> | Variable |
|
|
| BOOLEAN | bool | 1 byte |
|
|
| TIMESTAMP | u64 | 8 bytes (Unix ms) |
|
|
|
|
### Implementation Components
|
|
|
|
```
|
|
crates/synor-database/src/sql/
|
|
├── mod.rs # Module exports
|
|
├── parser.rs # SQL parsing (sqlparser-rs)
|
|
├── planner.rs # Query planning & optimization
|
|
├── executor.rs # Query execution engine
|
|
├── table.rs # Table definition & storage
|
|
├── row.rs # Row representation
|
|
├── types.rs # SQL type system
|
|
├── transaction.rs # ACID transactions
|
|
└── index.rs # SQL-specific indexing
|
|
```
|
|
|
|
### API Design
|
|
|
|
```rust
|
|
// Table definition
|
|
pub struct TableDef {
|
|
pub name: String,
|
|
pub columns: Vec<ColumnDef>,
|
|
pub primary_key: Option<String>,
|
|
pub indexes: Vec<IndexDef>,
|
|
}
|
|
|
|
pub struct ColumnDef {
|
|
pub name: String,
|
|
pub data_type: SqlType,
|
|
pub nullable: bool,
|
|
pub default: Option<SqlValue>,
|
|
}
|
|
|
|
// SQL execution
|
|
pub struct SqlEngine {
|
|
tables: HashMap<String, Table>,
|
|
transaction_log: TransactionLog,
|
|
}
|
|
|
|
impl SqlEngine {
|
|
pub fn execute(&mut self, sql: &str) -> Result<SqlResult, SqlError>;
|
|
pub fn begin_transaction(&mut self) -> TransactionId;
|
|
pub fn commit(&mut self, txn: TransactionId) -> Result<(), SqlError>;
|
|
pub fn rollback(&mut self, txn: TransactionId);
|
|
}
|
|
```
|
|
|
|
### Gateway Endpoints
|
|
|
|
| Endpoint | Method | Description |
|
|
|----------|--------|-------------|
|
|
| `/db/:db/sql` | POST | Execute SQL query |
|
|
| `/db/:db/sql/tables` | GET | List tables |
|
|
| `/db/:db/sql/tables/:table` | GET | Get table schema |
|
|
| `/db/:db/sql/tables/:table` | DELETE | Drop table |
|
|
|
|
---
|
|
|
|
## Feature 2: Graph Store
|
|
|
|
### Purpose
|
|
Enable relationship-based queries for social networks, knowledge graphs, recommendation engines, and any connected data.
|
|
|
|
### Architecture
|
|
|
|
```text
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ GRAPH QUERY LAYER │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
|
│ │ Graph Query │ │ Traversal │ │ Path Finding │ │
|
|
│ │ Parser │ │ Engine │ │ (Dijkstra) │ │
|
|
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ Graph Storage Engine │ │
|
|
│ │ - Adjacency list storage │ │
|
|
│ │ - Edge index (source, target, type) │ │
|
|
│ │ - Property storage │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Data Model
|
|
|
|
```text
|
|
Node (Vertex):
|
|
- id: NodeId (32 bytes)
|
|
- labels: Vec<String>
|
|
- properties: JsonValue
|
|
|
|
Edge (Relationship):
|
|
- id: EdgeId (32 bytes)
|
|
- source: NodeId
|
|
- target: NodeId
|
|
- edge_type: String
|
|
- properties: JsonValue
|
|
- directed: bool
|
|
```
|
|
|
|
### Query Language (Simplified Cypher-like)
|
|
|
|
```
|
|
// Find all friends of user Alice
|
|
MATCH (a:User {name: "Alice"})-[:FRIEND]->(friend)
|
|
RETURN friend
|
|
|
|
// Find shortest path between two nodes
|
|
MATCH path = shortestPath((a:User {id: "123"})-[*]-(b:User {id: "456"}))
|
|
RETURN path
|
|
|
|
// Find mutual friends
|
|
MATCH (a:User {name: "Alice"})-[:FRIEND]->(mutual)<-[:FRIEND]-(b:User {name: "Bob"})
|
|
RETURN mutual
|
|
```
|
|
|
|
### Implementation Components
|
|
|
|
```
|
|
crates/synor-database/src/graph/
|
|
├── mod.rs # Module exports
|
|
├── node.rs # Node definition & storage
|
|
├── edge.rs # Edge definition & storage
|
|
├── store.rs # Graph storage engine
|
|
├── query.rs # Query language parser
|
|
├── traversal.rs # Graph traversal algorithms
|
|
├── path.rs # Path finding (BFS, DFS, Dijkstra)
|
|
└── index.rs # Graph-specific indexes
|
|
```
|
|
|
|
### API Design
|
|
|
|
```rust
|
|
pub struct Node {
|
|
pub id: NodeId,
|
|
pub labels: Vec<String>,
|
|
pub properties: JsonValue,
|
|
}
|
|
|
|
pub struct Edge {
|
|
pub id: EdgeId,
|
|
pub source: NodeId,
|
|
pub target: NodeId,
|
|
pub edge_type: String,
|
|
pub properties: JsonValue,
|
|
}
|
|
|
|
pub struct GraphStore {
|
|
nodes: HashMap<NodeId, Node>,
|
|
edges: HashMap<EdgeId, Edge>,
|
|
adjacency: HashMap<NodeId, Vec<EdgeId>>, // outgoing
|
|
reverse_adj: HashMap<NodeId, Vec<EdgeId>>, // incoming
|
|
}
|
|
|
|
impl GraphStore {
|
|
// Node operations
|
|
pub fn create_node(&mut self, labels: Vec<String>, props: JsonValue) -> NodeId;
|
|
pub fn get_node(&self, id: &NodeId) -> Option<&Node>;
|
|
pub fn update_node(&mut self, id: &NodeId, props: JsonValue) -> Result<(), GraphError>;
|
|
pub fn delete_node(&mut self, id: &NodeId) -> Result<(), GraphError>;
|
|
|
|
// Edge operations
|
|
pub fn create_edge(&mut self, source: NodeId, target: NodeId, edge_type: &str, props: JsonValue) -> EdgeId;
|
|
pub fn get_edge(&self, id: &EdgeId) -> Option<&Edge>;
|
|
pub fn delete_edge(&mut self, id: &EdgeId) -> Result<(), GraphError>;
|
|
|
|
// Traversal
|
|
pub fn neighbors(&self, id: &NodeId, direction: Direction) -> Vec<&Node>;
|
|
pub fn edges_of(&self, id: &NodeId, direction: Direction) -> Vec<&Edge>;
|
|
pub fn shortest_path(&self, from: &NodeId, to: &NodeId) -> Option<Vec<NodeId>>;
|
|
pub fn traverse(&self, start: &NodeId, query: &TraversalQuery) -> Vec<TraversalResult>;
|
|
}
|
|
```
|
|
|
|
### Gateway Endpoints
|
|
|
|
| Endpoint | Method | Description |
|
|
|----------|--------|-------------|
|
|
| `/db/:db/graph/nodes` | POST | Create node |
|
|
| `/db/:db/graph/nodes/:id` | GET | Get node |
|
|
| `/db/:db/graph/nodes/:id` | PUT | Update node |
|
|
| `/db/:db/graph/nodes/:id` | DELETE | Delete node |
|
|
| `/db/:db/graph/edges` | POST | Create edge |
|
|
| `/db/:db/graph/edges/:id` | GET | Get edge |
|
|
| `/db/:db/graph/edges/:id` | DELETE | Delete edge |
|
|
| `/db/:db/graph/query` | POST | Execute graph query |
|
|
| `/db/:db/graph/path` | POST | Find shortest path |
|
|
| `/db/:db/graph/traverse` | POST | Traverse from node |
|
|
|
|
---
|
|
|
|
## Feature 3: Replication (Raft Consensus)
|
|
|
|
### Purpose
|
|
Provide high availability and fault tolerance through distributed consensus, ensuring data consistency across multiple nodes.
|
|
|
|
### Architecture
|
|
|
|
```text
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ RAFT CONSENSUS LAYER │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
|
│ │ Leader │ │ Follower │ │ Candidate │ │
|
|
│ │ Election │ │ Replication │ │ (Election) │ │
|
|
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ Log Replication │ │
|
|
│ │ - Append entries │ │
|
|
│ │ - Commit index │ │
|
|
│ │ - Log compaction (snapshots) │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ State Machine │ │
|
|
│ │ - Apply committed entries │ │
|
|
│ │ - Database operations │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Raft Protocol Overview
|
|
|
|
```text
|
|
Leader Election:
|
|
1. Followers timeout → become Candidate
|
|
2. Candidate requests votes from peers
|
|
3. Majority votes → become Leader
|
|
4. Leader sends heartbeats to maintain authority
|
|
|
|
Log Replication:
|
|
1. Client sends write to Leader
|
|
2. Leader appends to local log
|
|
3. Leader replicates to Followers
|
|
4. Majority acknowledge → entry committed
|
|
5. Leader applies to state machine
|
|
6. Leader responds to client
|
|
```
|
|
|
|
### Implementation Components
|
|
|
|
```
|
|
crates/synor-database/src/replication/
|
|
├── mod.rs # Module exports
|
|
├── raft.rs # Core Raft implementation
|
|
├── state.rs # Node state (Leader/Follower/Candidate)
|
|
├── log.rs # Replicated log
|
|
├── rpc.rs # RPC messages (AppendEntries, RequestVote)
|
|
├── election.rs # Leader election logic
|
|
├── snapshot.rs # Log compaction & snapshots
|
|
├── cluster.rs # Cluster membership
|
|
└── client.rs # Client for forwarding to leader
|
|
```
|
|
|
|
### API Design
|
|
|
|
```rust
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
pub enum NodeRole {
|
|
Leader,
|
|
Follower,
|
|
Candidate,
|
|
}
|
|
|
|
pub struct RaftConfig {
|
|
pub node_id: u64,
|
|
pub peers: Vec<PeerAddress>,
|
|
pub election_timeout_ms: (u64, u64), // min, max
|
|
pub heartbeat_interval_ms: u64,
|
|
pub snapshot_threshold: u64,
|
|
}
|
|
|
|
pub struct LogEntry {
|
|
pub term: u64,
|
|
pub index: u64,
|
|
pub command: Command,
|
|
}
|
|
|
|
pub enum Command {
|
|
// Database operations
|
|
KvSet { key: String, value: Vec<u8> },
|
|
KvDelete { key: String },
|
|
DocInsert { collection: String, doc: JsonValue },
|
|
DocUpdate { collection: String, id: DocumentId, update: JsonValue },
|
|
DocDelete { collection: String, id: DocumentId },
|
|
// ... other operations
|
|
}
|
|
|
|
pub struct RaftNode {
|
|
config: RaftConfig,
|
|
state: RaftState,
|
|
log: ReplicatedLog,
|
|
state_machine: Arc<Database>,
|
|
}
|
|
|
|
impl RaftNode {
|
|
pub async fn start(&mut self) -> Result<(), RaftError>;
|
|
pub async fn propose(&self, command: Command) -> Result<(), RaftError>;
|
|
pub fn is_leader(&self) -> bool;
|
|
pub fn leader_id(&self) -> Option<u64>;
|
|
pub fn status(&self) -> ClusterStatus;
|
|
}
|
|
|
|
// RPC Messages
|
|
pub struct AppendEntries {
|
|
pub term: u64,
|
|
pub leader_id: u64,
|
|
pub prev_log_index: u64,
|
|
pub prev_log_term: u64,
|
|
pub entries: Vec<LogEntry>,
|
|
pub leader_commit: u64,
|
|
}
|
|
|
|
pub struct RequestVote {
|
|
pub term: u64,
|
|
pub candidate_id: u64,
|
|
pub last_log_index: u64,
|
|
pub last_log_term: u64,
|
|
}
|
|
```
|
|
|
|
### Cluster Configuration
|
|
|
|
```yaml
|
|
# docker-compose.raft.yml
|
|
services:
|
|
db-node-1:
|
|
image: synor/database:latest
|
|
environment:
|
|
RAFT_NODE_ID: 1
|
|
RAFT_PEERS: "db-node-2:5000,db-node-3:5000"
|
|
RAFT_ELECTION_TIMEOUT: "150-300"
|
|
RAFT_HEARTBEAT_MS: 50
|
|
ports:
|
|
- "8484:8484" # HTTP API
|
|
- "5000:5000" # Raft RPC
|
|
|
|
db-node-2:
|
|
image: synor/database:latest
|
|
environment:
|
|
RAFT_NODE_ID: 2
|
|
RAFT_PEERS: "db-node-1:5000,db-node-3:5000"
|
|
ports:
|
|
- "8485:8484"
|
|
- "5001:5000"
|
|
|
|
db-node-3:
|
|
image: synor/database:latest
|
|
environment:
|
|
RAFT_NODE_ID: 3
|
|
RAFT_PEERS: "db-node-1:5000,db-node-2:5000"
|
|
ports:
|
|
- "8486:8484"
|
|
- "5002:5000"
|
|
```
|
|
|
|
### Gateway Endpoints
|
|
|
|
| Endpoint | Method | Description |
|
|
|----------|--------|-------------|
|
|
| `/cluster/status` | GET | Get cluster status |
|
|
| `/cluster/leader` | GET | Get current leader |
|
|
| `/cluster/nodes` | GET | List all nodes |
|
|
| `/cluster/nodes/:id` | DELETE | Remove node from cluster |
|
|
| `/cluster/nodes` | POST | Add node to cluster |
|
|
|
|
---
|
|
|
|
## Implementation Order
|
|
|
|
### Step 1: SQL Store
|
|
1. Add `sqlparser` dependency
|
|
2. Implement type system (`types.rs`)
|
|
3. Implement row storage (`row.rs`, `table.rs`)
|
|
4. Implement SQL parser wrapper (`parser.rs`)
|
|
5. Implement query planner (`planner.rs`)
|
|
6. Implement query executor (`executor.rs`)
|
|
7. Add transaction support (`transaction.rs`)
|
|
8. Add gateway endpoints
|
|
9. Write tests
|
|
|
|
### Step 2: Graph Store
|
|
1. Implement node/edge types (`node.rs`, `edge.rs`)
|
|
2. Implement graph storage (`store.rs`)
|
|
3. Implement query parser (`query.rs`)
|
|
4. Implement traversal algorithms (`traversal.rs`, `path.rs`)
|
|
5. Add graph indexes (`index.rs`)
|
|
6. Add gateway endpoints
|
|
7. Write tests
|
|
|
|
### Step 3: Replication
|
|
1. Implement Raft state machine (`state.rs`, `raft.rs`)
|
|
2. Implement replicated log (`log.rs`)
|
|
3. Implement RPC layer (`rpc.rs`)
|
|
4. Implement leader election (`election.rs`)
|
|
5. Implement log compaction (`snapshot.rs`)
|
|
6. Implement cluster management (`cluster.rs`)
|
|
7. Integrate with database operations
|
|
8. Add gateway endpoints
|
|
9. Write tests
|
|
10. Create Docker Compose for cluster
|
|
|
|
---
|
|
|
|
## Pricing Impact
|
|
|
|
| Feature | Operation | Cost (SYNOR) |
|
|
|---------|-----------|--------------|
|
|
| SQL | Query/million | 0.02 |
|
|
| SQL | Write/million | 0.05 |
|
|
| Graph | Traversal/million | 0.03 |
|
|
| Graph | Path query/million | 0.05 |
|
|
| Replication | Included | Base storage cost |
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
### SQL Store
|
|
- [ ] Parse and execute basic SELECT, INSERT, UPDATE, DELETE
|
|
- [ ] Support WHERE clauses with operators
|
|
- [ ] Support ORDER BY, LIMIT, OFFSET
|
|
- [ ] Support simple JOINs
|
|
- [ ] Support aggregate functions
|
|
- [ ] ACID transactions
|
|
|
|
### Graph Store
|
|
- [ ] Create/read/update/delete nodes and edges
|
|
- [ ] Traverse neighbors (in/out/both)
|
|
- [ ] Find shortest path between nodes
|
|
- [ ] Execute pattern matching queries
|
|
- [ ] Support property filters
|
|
|
|
### Replication
|
|
- [ ] Leader election works correctly
|
|
- [ ] Log replication achieves consensus
|
|
- [ ] Reads from any node (eventual consistency)
|
|
- [ ] Writes only through leader
|
|
- [ ] Node failure handled gracefully
|
|
- [ ] Log compaction reduces storage
|