mirror of
https://github.com/azaion/gps-denied-desktop.git
synced 2026-04-22 21:56:37 +00:00
component decomposition is done
This commit is contained in:
@@ -0,0 +1,475 @@
|
||||
# Route Database Layer
|
||||
|
||||
## Interface Definition
|
||||
|
||||
**Interface Name**: `IRouteDatabase`
|
||||
|
||||
### Interface Methods
|
||||
|
||||
```python
|
||||
class IRouteDatabase(ABC):
|
||||
@abstractmethod
|
||||
def insert_route(self, route: Route) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_route(self, route: Route) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def query_routes(self, filters: Dict[str, Any], limit: int, offset: int) -> List[Route]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_route_by_id(self, route_id: str) -> Optional[Route]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_waypoints(self, route_id: str, limit: Optional[int] = None) -> List[Waypoint]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def insert_waypoint(self, route_id: str, waypoint: Waypoint) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_waypoint(self, route_id: str, waypoint_id: str, waypoint: Waypoint) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_route(self, route_id: str) -> bool:
|
||||
pass
|
||||
```
|
||||
|
||||
## Component Description
|
||||
|
||||
### Responsibilities
|
||||
- Direct database access layer for route data
|
||||
- Execute SQL queries and commands
|
||||
- Manage database connections and transactions
|
||||
- Handle connection pooling and retry logic
|
||||
- Provide database abstraction for potential migration (PostgreSQL, MySQL, etc.)
|
||||
|
||||
### Scope
|
||||
- CRUD operations on routes table
|
||||
- CRUD operations on waypoints table
|
||||
- CRUD operations on geofences table
|
||||
- Query optimization for large datasets
|
||||
- Database schema management
|
||||
- Separate schema from GPS-Denied API database
|
||||
|
||||
## API Methods
|
||||
|
||||
### `insert_route(route: Route) -> str`
|
||||
|
||||
**Description**: Inserts a new route with initial waypoints and geofences into the database.
|
||||
|
||||
**Called By**:
|
||||
- R02 Route Data Manager
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
Route:
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
points: List[Waypoint]
|
||||
geofences: Geofences
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
route_id: str # Inserted route ID
|
||||
```
|
||||
|
||||
**Database Operations**:
|
||||
1. Begin transaction
|
||||
2. INSERT INTO routes (id, name, description, created_at, updated_at)
|
||||
3. INSERT INTO waypoints (route_id, ...) for each waypoint
|
||||
4. INSERT INTO geofences (route_id, ...) for each polygon
|
||||
5. Commit transaction
|
||||
|
||||
**Error Conditions**:
|
||||
- `IntegrityError`: Duplicate route_id (unique constraint violation)
|
||||
- `DatabaseError`: Connection error, transaction failure
|
||||
- Automatic rollback on any error
|
||||
|
||||
**Test Cases**:
|
||||
1. **Insert route with 100 waypoints**: Successful insertion, all waypoints persisted
|
||||
2. **Duplicate route_id**: Raises IntegrityError
|
||||
3. **Transaction rollback**: Error on waypoint insertion → route also rolled back
|
||||
4. **Connection loss**: Mid-transaction error → graceful rollback
|
||||
|
||||
---
|
||||
|
||||
### `update_route(route: Route) -> bool`
|
||||
|
||||
**Description**: Updates route metadata (name, description, updated_at).
|
||||
|
||||
**Called By**:
|
||||
- R02 Route Data Manager
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
Route with updated fields
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
bool: True if updated, False if route not found
|
||||
```
|
||||
|
||||
**Database Operations**:
|
||||
```sql
|
||||
UPDATE routes
|
||||
SET name = ?, description = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
```
|
||||
|
||||
**Error Conditions**:
|
||||
- `DatabaseError`: Connection or query error
|
||||
|
||||
**Test Cases**:
|
||||
1. **Update existing route**: Returns True
|
||||
2. **Update non-existent route**: Returns False
|
||||
3. **Update with same data**: Succeeds, updates timestamp
|
||||
|
||||
---
|
||||
|
||||
### `query_routes(filters: Dict[str, Any], limit: int, offset: int) -> List[Route]`
|
||||
|
||||
**Description**: Queries routes with filtering, pagination for route listing.
|
||||
|
||||
**Called By**:
|
||||
- R02 Route Data Manager
|
||||
- R01 Route REST API (list endpoints)
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
filters: Dict[str, Any] # e.g., {"name": "Mission%", "created_after": datetime}
|
||||
limit: int # Max results
|
||||
offset: int # For pagination
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
List[Route] # Routes without full waypoint data (metadata only)
|
||||
```
|
||||
|
||||
**Database Operations**:
|
||||
```sql
|
||||
SELECT * FROM routes
|
||||
WHERE name LIKE ? AND created_at > ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
```
|
||||
|
||||
**Error Conditions**:
|
||||
- `DatabaseError`: Query error
|
||||
|
||||
**Test Cases**:
|
||||
1. **Filter by name**: Returns matching routes
|
||||
2. **Pagination**: offset=100, limit=50 → returns routes 100-149
|
||||
3. **Empty result**: No matches → returns []
|
||||
4. **No filters**: Returns all routes (with limit)
|
||||
|
||||
---
|
||||
|
||||
### `get_route_by_id(route_id: str) -> Optional[Route]`
|
||||
|
||||
**Description**: Retrieves complete route with all waypoints by ID.
|
||||
|
||||
**Called By**:
|
||||
- R02 Route Data Manager
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
route_id: str
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
Optional[Route] # Complete route with all waypoints, or None
|
||||
```
|
||||
|
||||
**Database Operations**:
|
||||
1. SELECT FROM routes WHERE id = ?
|
||||
2. SELECT FROM waypoints WHERE route_id = ? ORDER BY timestamp
|
||||
3. SELECT FROM geofences WHERE route_id = ?
|
||||
4. Assemble Route object
|
||||
|
||||
**Error Conditions**:
|
||||
- `DatabaseError`: Query error
|
||||
- Returns None if route not found
|
||||
|
||||
**Test Cases**:
|
||||
1. **Existing route**: Returns complete Route object
|
||||
2. **Non-existent route**: Returns None
|
||||
3. **Large route (3000 waypoints)**: Returns all data within 150ms
|
||||
4. **Route with no waypoints**: Returns route with empty points list
|
||||
|
||||
---
|
||||
|
||||
### `get_waypoints(route_id: str, limit: Optional[int] = None) -> List[Waypoint]`
|
||||
|
||||
**Description**: Retrieves waypoints for a route, optionally limited.
|
||||
|
||||
**Called By**:
|
||||
- R02 Route Data Manager
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
route_id: str
|
||||
limit: Optional[int] # For pagination or preview
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
List[Waypoint]
|
||||
```
|
||||
|
||||
**Database Operations**:
|
||||
```sql
|
||||
SELECT * FROM waypoints
|
||||
WHERE route_id = ?
|
||||
ORDER BY timestamp ASC
|
||||
LIMIT ? -- if limit provided
|
||||
```
|
||||
|
||||
**Error Conditions**:
|
||||
- `DatabaseError`: Query error
|
||||
|
||||
**Test Cases**:
|
||||
1. **All waypoints**: limit=None → returns all
|
||||
2. **Limited waypoints**: limit=100 → returns first 100
|
||||
3. **No waypoints**: Empty list
|
||||
|
||||
---
|
||||
|
||||
### `insert_waypoint(route_id: str, waypoint: Waypoint) -> str`
|
||||
|
||||
**Description**: Inserts a new waypoint into a route.
|
||||
|
||||
**Called By**:
|
||||
- R02 Route Data Manager
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
route_id: str
|
||||
waypoint: Waypoint
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
waypoint_id: str
|
||||
```
|
||||
|
||||
**Database Operations**:
|
||||
```sql
|
||||
INSERT INTO waypoints (id, route_id, lat, lon, altitude, confidence, timestamp, refined)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
```
|
||||
|
||||
**Error Conditions**:
|
||||
- `ForeignKeyError`: route_id doesn't exist
|
||||
- `IntegrityError`: Duplicate waypoint_id
|
||||
|
||||
**Test Cases**:
|
||||
1. **Valid insertion**: Returns waypoint_id
|
||||
2. **Non-existent route**: Raises ForeignKeyError
|
||||
|
||||
---
|
||||
|
||||
### `update_waypoint(route_id: str, waypoint_id: str, waypoint: Waypoint) -> bool`
|
||||
|
||||
**Description**: Updates a waypoint. Critical path for GPS-Denied per-frame updates.
|
||||
|
||||
**Called By**:
|
||||
- R02 Route Data Manager
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
route_id: str
|
||||
waypoint_id: str
|
||||
waypoint: Waypoint
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
bool: True if updated, False if not found
|
||||
```
|
||||
|
||||
**Database Operations**:
|
||||
```sql
|
||||
UPDATE waypoints
|
||||
SET lat = ?, lon = ?, altitude = ?, confidence = ?, refined = ?
|
||||
WHERE id = ? AND route_id = ?
|
||||
```
|
||||
|
||||
**Optimization**:
|
||||
- Prepared statement caching
|
||||
- Connection pooling for high throughput
|
||||
- Indexed on (route_id, id) for fast lookups
|
||||
|
||||
**Error Conditions**:
|
||||
- `DatabaseError`: Query error
|
||||
|
||||
**Test Cases**:
|
||||
1. **Update existing waypoint**: Returns True, updates data
|
||||
2. **Non-existent waypoint**: Returns False
|
||||
3. **High-frequency updates**: 100 updates/sec sustained for 20 seconds
|
||||
|
||||
---
|
||||
|
||||
### `delete_route(route_id: str) -> bool`
|
||||
|
||||
**Description**: Deletes a route and cascades to waypoints and geofences.
|
||||
|
||||
**Called By**:
|
||||
- R02 Route Data Manager
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
route_id: str
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
bool: True if deleted, False if not found
|
||||
```
|
||||
|
||||
**Database Operations**:
|
||||
```sql
|
||||
DELETE FROM routes WHERE id = ?
|
||||
-- Cascade deletes from waypoints and geofences via FK constraints
|
||||
```
|
||||
|
||||
**Error Conditions**:
|
||||
- `DatabaseError`: Query error
|
||||
|
||||
**Test Cases**:
|
||||
1. **Delete route with waypoints**: Deletes route and all waypoints
|
||||
2. **Verify cascade**: Check waypoints table empty for route_id
|
||||
3. **Non-existent route**: Returns False
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test 1: Complete Database Lifecycle
|
||||
1. insert_route() with 500 waypoints
|
||||
2. get_route_by_id() and verify all data
|
||||
3. update_waypoint() × 100
|
||||
4. query_routes() with filters
|
||||
5. delete_route() and verify removal
|
||||
|
||||
### Test 2: High-Frequency Update Pattern (GPS-Denied Simulation)
|
||||
1. insert_route() with 2000 waypoints
|
||||
2. update_waypoint() × 2000 sequentially
|
||||
3. Measure total time and throughput
|
||||
4. Verify all updates persisted correctly
|
||||
|
||||
### Test 3: Concurrent Access
|
||||
1. Insert 10 routes concurrently
|
||||
2. Update waypoints in parallel (100 concurrent connections)
|
||||
3. Query routes while updates occurring
|
||||
4. Verify no deadlocks or data corruption
|
||||
|
||||
### Test 4: Transaction Integrity
|
||||
1. Begin insert_route() transaction
|
||||
2. Simulate error mid-waypoint insertion
|
||||
3. Verify complete rollback (no partial data)
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
### Performance
|
||||
- **insert_route**: < 200ms for 100 waypoints
|
||||
- **update_waypoint**: < 30ms (critical path)
|
||||
- **get_route_by_id**: < 100ms for 2000 waypoints
|
||||
- **query_routes**: < 150ms for pagination queries
|
||||
- **Throughput**: 200+ waypoint updates per second
|
||||
|
||||
### Scalability
|
||||
- Connection pool: 50-100 connections
|
||||
- Support 1000+ concurrent operations
|
||||
- Handle tables with millions of waypoints
|
||||
|
||||
### Reliability
|
||||
- ACID transaction guarantees
|
||||
- Automatic retry on transient errors (3 attempts with exponential backoff)
|
||||
- Connection health checks
|
||||
- Graceful degradation on connection pool exhaustion
|
||||
|
||||
### Security
|
||||
- SQL injection prevention (parameterized queries only)
|
||||
- Principle of least privilege (database user permissions)
|
||||
- Connection string encryption
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Internal Components
|
||||
- None (lowest layer)
|
||||
|
||||
### External Dependencies
|
||||
- **PostgreSQL** or **MySQL**: Relational database
|
||||
- **SQLAlchemy** or **psycopg2**: Database driver
|
||||
- **Alembic**: Schema migration tool
|
||||
|
||||
## Data Models
|
||||
|
||||
### Database Schema
|
||||
|
||||
```sql
|
||||
-- Routes table
|
||||
CREATE TABLE routes (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_created_at (created_at),
|
||||
INDEX idx_name (name)
|
||||
);
|
||||
|
||||
-- Waypoints table
|
||||
CREATE TABLE waypoints (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
route_id VARCHAR(36) NOT NULL,
|
||||
lat DECIMAL(10, 7) NOT NULL,
|
||||
lon DECIMAL(11, 7) NOT NULL,
|
||||
altitude DECIMAL(7, 2),
|
||||
confidence DECIMAL(3, 2) NOT NULL,
|
||||
timestamp TIMESTAMP NOT NULL,
|
||||
refined BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
FOREIGN KEY (route_id) REFERENCES routes(id) ON DELETE CASCADE,
|
||||
INDEX idx_route_timestamp (route_id, timestamp),
|
||||
INDEX idx_route_id (route_id, id)
|
||||
);
|
||||
|
||||
-- Geofences table
|
||||
CREATE TABLE geofences (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
route_id VARCHAR(36) NOT NULL,
|
||||
nw_lat DECIMAL(10, 7) NOT NULL,
|
||||
nw_lon DECIMAL(11, 7) NOT NULL,
|
||||
se_lat DECIMAL(10, 7) NOT NULL,
|
||||
se_lon DECIMAL(11, 7) NOT NULL,
|
||||
FOREIGN KEY (route_id) REFERENCES routes(id) ON DELETE CASCADE,
|
||||
INDEX idx_route_id (route_id)
|
||||
);
|
||||
```
|
||||
|
||||
### Connection Configuration
|
||||
```python
|
||||
class DatabaseConfig(BaseModel):
|
||||
host: str
|
||||
port: int
|
||||
database: str
|
||||
username: str
|
||||
password: str
|
||||
pool_size: int = 50
|
||||
max_overflow: int = 50
|
||||
pool_timeout: int = 30
|
||||
pool_recycle: int = 3600
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user