skyview/internal/database/database.go
Ole-Morten Duesund 0f16748224 feat: Enhance core database functionality and optimization
- Add comprehensive database optimization management
- Enhance external data source loading with progress tracking
- Add optimization statistics and efficiency calculations
- Update Go module dependencies for database operations
- Implement database size and performance monitoring

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-31 19:43:24 +02:00

261 lines
No EOL
7.4 KiB
Go

// Package database provides persistent storage for aircraft data and callsign enhancement
// using SQLite with versioned schema migrations and comprehensive error handling.
//
// The database system supports:
// - Aircraft position history with configurable retention
// - Embedded OpenFlights airline and airport data
// - External API result caching with TTL
// - Schema migrations with rollback support
// - Privacy mode for complete offline operation
package database
import (
"database/sql"
"fmt"
"time"
_ "github.com/mattn/go-sqlite3" // SQLite driver
)
// Database represents the main database connection and operations
type Database struct {
conn *sql.DB
config *Config
migrator *Migrator
callsign *CallsignManager
history *HistoryManager
}
// Config holds database configuration options
type Config struct {
// Database file path (auto-resolved if empty)
Path string `json:"path"`
// Data retention settings
MaxHistoryDays int `json:"max_history_days"` // 0 = unlimited
BackupOnUpgrade bool `json:"backup_on_upgrade"`
// Connection settings
MaxOpenConns int `json:"max_open_conns"` // Default: 10
MaxIdleConns int `json:"max_idle_conns"` // Default: 5
ConnMaxLifetime time.Duration `json:"conn_max_lifetime"` // Default: 1 hour
// Maintenance settings
VacuumInterval time.Duration `json:"vacuum_interval"` // Default: 24 hours
CleanupInterval time.Duration `json:"cleanup_interval"` // Default: 1 hour
// Compression settings
EnableCompression bool `json:"enable_compression"` // Enable automatic compression
CompressionLevel int `json:"compression_level"` // Compression level (1-9, default: 6)
PageSize int `json:"page_size"` // SQLite page size (default: 4096)
}
// AircraftHistoryRecord represents a stored aircraft position update
type AircraftHistoryRecord struct {
ID int64 `json:"id"`
ICAO string `json:"icao"`
Timestamp time.Time `json:"timestamp"`
Latitude *float64 `json:"latitude,omitempty"`
Longitude *float64 `json:"longitude,omitempty"`
Altitude *int `json:"altitude,omitempty"`
Speed *int `json:"speed,omitempty"`
Track *int `json:"track,omitempty"`
VerticalRate *int `json:"vertical_rate,omitempty"`
Squawk *string `json:"squawk,omitempty"`
Callsign *string `json:"callsign,omitempty"`
SourceID string `json:"source_id"`
SignalStrength *float64 `json:"signal_strength,omitempty"`
}
// CallsignInfo represents enriched callsign information
type CallsignInfo struct {
OriginalCallsign string `json:"original_callsign"`
AirlineCode string `json:"airline_code"`
FlightNumber string `json:"flight_number"`
AirlineName string `json:"airline_name"`
AirlineCountry string `json:"airline_country"`
DisplayName string `json:"display_name"`
IsValid bool `json:"is_valid"`
LastUpdated time.Time `json:"last_updated"`
}
// AirlineRecord represents embedded airline data from OpenFlights
type AirlineRecord struct {
ID int `json:"id"`
Name string `json:"name"`
Alias string `json:"alias"`
IATACode string `json:"iata_code"`
ICAOCode string `json:"icao_code"`
Callsign string `json:"callsign"`
Country string `json:"country"`
Active bool `json:"active"`
}
// AirportRecord represents embedded airport data from OpenFlights
type AirportRecord struct {
ID int `json:"id"`
Name string `json:"name"`
City string `json:"city"`
Country string `json:"country"`
IATA string `json:"iata"`
ICAO string `json:"icao"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Altitude int `json:"altitude"`
TimezoneOffset float64 `json:"timezone_offset"`
DST string `json:"dst"`
Timezone string `json:"timezone"`
}
// DatabaseError represents database operation errors
type DatabaseError struct {
Operation string
Err error
Query string
Retryable bool
}
func (e *DatabaseError) Error() string {
if e.Query != "" {
return fmt.Sprintf("database %s error: %v (query: %s)", e.Operation, e.Err, e.Query)
}
return fmt.Sprintf("database %s error: %v", e.Operation, e.Err)
}
func (e *DatabaseError) Unwrap() error {
return e.Err
}
// NewDatabase creates a new database connection with the given configuration
func NewDatabase(config *Config) (*Database, error) {
if config == nil {
config = DefaultConfig()
}
// Resolve database path
dbPath, err := ResolveDatabasePath(config.Path)
if err != nil {
return nil, &DatabaseError{
Operation: "path_resolution",
Err: err,
Retryable: false,
}
}
config.Path = dbPath
// Open database connection
conn, err := sql.Open("sqlite3", buildConnectionString(dbPath))
if err != nil {
return nil, &DatabaseError{
Operation: "connect",
Err: err,
Retryable: true,
}
}
// Configure connection pool
conn.SetMaxOpenConns(config.MaxOpenConns)
conn.SetMaxIdleConns(config.MaxIdleConns)
conn.SetConnMaxLifetime(config.ConnMaxLifetime)
// Test connection
if err := conn.Ping(); err != nil {
conn.Close()
return nil, &DatabaseError{
Operation: "ping",
Err: err,
Retryable: true,
}
}
db := &Database{
conn: conn,
config: config,
}
// Initialize components
db.migrator = NewMigrator(conn)
db.callsign = NewCallsignManager(conn)
db.history = NewHistoryManager(conn, config.MaxHistoryDays)
return db, nil
}
// Initialize runs database migrations and sets up embedded data
func (db *Database) Initialize() error {
// Run schema migrations
if err := db.migrator.MigrateToLatest(); err != nil {
return &DatabaseError{
Operation: "migration",
Err: err,
Retryable: false,
}
}
// Load embedded OpenFlights data if not already loaded
if err := db.callsign.LoadEmbeddedData(); err != nil {
return &DatabaseError{
Operation: "load_embedded_data",
Err: err,
Retryable: false,
}
}
return nil
}
// GetConfig returns the database configuration
func (db *Database) GetConfig() *Config {
return db.config
}
// GetConnection returns the underlying database connection
func (db *Database) GetConnection() *sql.DB {
return db.conn
}
// GetHistoryManager returns the history manager
func (db *Database) GetHistoryManager() *HistoryManager {
return db.history
}
// GetCallsignManager returns the callsign manager
func (db *Database) GetCallsignManager() *CallsignManager {
return db.callsign
}
// Close closes the database connection and stops background tasks
func (db *Database) Close() error {
if db.conn != nil {
return db.conn.Close()
}
return nil
}
// Health returns the database health status
func (db *Database) Health() error {
if db.conn == nil {
return fmt.Errorf("database connection not initialized")
}
return db.conn.Ping()
}
// DefaultConfig returns the default database configuration
func DefaultConfig() *Config {
return &Config{
Path: "", // Auto-resolved
MaxHistoryDays: 7,
BackupOnUpgrade: true,
MaxOpenConns: 10,
MaxIdleConns: 5,
ConnMaxLifetime: time.Hour,
VacuumInterval: 24 * time.Hour,
CleanupInterval: time.Hour,
}
}
// buildConnectionString creates SQLite connection string with optimizations
func buildConnectionString(path string) string {
return fmt.Sprintf("%s?_journal_mode=WAL&_synchronous=NORMAL&_cache_size=-64000&_temp_store=MEMORY&_foreign_keys=ON", path)
}