- 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.
18 KiB
18 KiB
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:
- Relational (SQL) - SQLite-compatible query subset for structured data
- Graph Store - Relationship queries for connected data
- 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
┌─────────────────────────────────────────────────────────────┐
│ 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 | 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
// 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
┌─────────────────────────────────────────────────────────────┐
│ 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
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
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
┌─────────────────────────────────────────────────────────────┐
│ 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
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
#[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
# 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
- Add
sqlparserdependency - Implement type system (
types.rs) - Implement row storage (
row.rs,table.rs) - Implement SQL parser wrapper (
parser.rs) - Implement query planner (
planner.rs) - Implement query executor (
executor.rs) - Add transaction support (
transaction.rs) - Add gateway endpoints
- Write tests
Step 2: Graph Store
- Implement node/edge types (
node.rs,edge.rs) - Implement graph storage (
store.rs) - Implement query parser (
query.rs) - Implement traversal algorithms (
traversal.rs,path.rs) - Add graph indexes (
index.rs) - Add gateway endpoints
- Write tests
Step 3: Replication
- Implement Raft state machine (
state.rs,raft.rs) - Implement replicated log (
log.rs) - Implement RPC layer (
rpc.rs) - Implement leader election (
election.rs) - Implement log compaction (
snapshot.rs) - Implement cluster management (
cluster.rs) - Integrate with database operations
- Add gateway endpoints
- Write tests
- 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