master
go 201 lines 4.99 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package dbdriver
4
5 import (
6 "context"
7 "database/sql"
8 "fmt"
9 "slices"
10 "sync"
11 )
12
13 // Driver represents a database driver with its capabilities
14 type Driver struct {
15 Name string
16 Description string
17 Available bool
18 RequiresCGO bool
19 RequiresLibs []string // Required system libraries
20 DSNFormat string // Example DSN format
21 }
22
23 // Registry manages available database drivers
24 type Registry struct {
25 mu sync.RWMutex
26 drivers map[string]*Driver
27 }
28
29 var defaultRegistry = &Registry{
30 drivers: make(map[string]*Driver),
31 }
32
33 // DBConnection wraps database connection with driver info
34 type DBConnection struct {
35 *sql.DB
36 Driver string
37 DSN string // Sanitized DSN for logging
38 DriverInfo *Driver
39 }
40
41 // Register adds a driver to the registry
42 func Register(name string, driver *Driver) {
43 defaultRegistry.mu.Lock()
44 defer defaultRegistry.mu.Unlock()
45 defaultRegistry.drivers[name] = driver
46 }
47
48 // GetAvailableDrivers returns list of available drivers
49 func GetAvailableDrivers() []string {
50 defaultRegistry.mu.RLock()
51 defer defaultRegistry.mu.RUnlock()
52
53 var available []string
54 for name, driver := range defaultRegistry.drivers {
55 if driver.Available {
56 available = append(available, name)
57 }
58 }
59 return available
60 }
61
62 // IsDriverAvailable checks if a specific driver is available
63 func IsDriverAvailable(name string) bool {
64 defaultRegistry.mu.RLock()
65 defer defaultRegistry.mu.RUnlock()
66
67 if driver, exists := defaultRegistry.drivers[name]; exists {
68 return driver.Available
69 }
70 return false
71 }
72
73 // GetDriverInfo returns information about a specific driver
74 func GetDriverInfo(name string) *Driver {
75 defaultRegistry.mu.RLock()
76 defer defaultRegistry.mu.RUnlock()
77
78 if driver, exists := defaultRegistry.drivers[name]; exists {
79 return driver
80 }
81 return nil
82 }
83
84 // Connect creates a database connection using the best available driver
85 func Connect(ctx context.Context, config *ConnectionConfig) (*DBConnection, error) {
86 // Determine which driver to use
87 driverName, dsn, err := determineDriver(config)
88 if err != nil {
89 return nil, err
90 }
91
92 // Get driver info
93 driverInfo := GetDriverInfo(driverName)
94 if driverInfo == nil || !driverInfo.Available {
95 return nil, fmt.Errorf("driver %s not available", driverName)
96 }
97
98 // Open database connection
99 db, err := sql.Open(driverName, dsn)
100 if err != nil {
101 return nil, fmt.Errorf("failed to open database with %s: %w", driverName, err)
102 }
103
104 // Configure connection pool
105 if config.MaxOpenConns > 0 {
106 db.SetMaxOpenConns(config.MaxOpenConns)
107 }
108 if config.MaxIdleConns > 0 {
109 db.SetMaxIdleConns(config.MaxIdleConns)
110 }
111 if config.ConnMaxLifetime > 0 {
112 db.SetConnMaxLifetime(config.ConnMaxLifetime)
113 }
114
115 // Test connection
116 pingCtx, cancel := context.WithTimeout(ctx, config.Timeout)
117 defer cancel()
118
119 if err := db.PingContext(pingCtx); err != nil {
120 db.Close()
121 return nil, fmt.Errorf("failed to ping database with %s: %w", driverName, err)
122 }
123
124 return &DBConnection{
125 DB: db,
126 Driver: driverName,
127 DSN: SanitizeDSN(dsn),
128 DriverInfo: driverInfo,
129 }, nil
130 }
131
132 // determineDriver selects the best driver based on configuration and availability
133 func determineDriver(config *ConnectionConfig) (driver, dsn string, err error) {
134 available := GetAvailableDrivers()
135 if len(available) == 0 {
136 return "", "", fmt.Errorf("no database drivers available. Install IBM DB2 client or configure ODBC")
137 }
138
139 // If DSN provided, try to auto-detect
140 if config.DSN != "" {
141 driver, dsn = detectDriverFromDSN(config.DSN)
142 if driver != "" && IsDriverAvailable(driver) {
143 return driver, dsn, nil
144 }
145 }
146
147 // Build based on connection type
148 switch config.ConnectionType {
149 case "db2":
150 if !IsDriverAvailable("go_ibm_db") {
151 return "", "", fmt.Errorf("IBM DB2 client driver requested but not available. Available drivers: %v", available)
152 }
153 return "go_ibm_db", BuildDB2DSN(config), nil
154
155 case "odbc":
156 if !IsDriverAvailable("odbc") {
157 return "", "", fmt.Errorf("ODBC driver requested but not available. Available drivers: %v", available)
158 }
159 return "odbc", BuildODBCDSN(config), nil
160
161 case "auto", "":
162 // Auto-select best available driver
163 // Prefer ODBC if configured or for AS/400
164 if config.PreferODBC || config.SystemType == "AS400" {
165 if slices.Contains(available, "odbc") {
166 return "odbc", BuildODBCDSN(config), nil
167 }
168 }
169
170 // Try IBM DB2 client
171 if slices.Contains(available, "go_ibm_db") {
172 return "go_ibm_db", BuildDB2DSN(config), nil
173 }
174
175 // Use whatever is available
176 driver := available[0]
177 if driver == "odbc" {
178 return driver, BuildODBCDSN(config), nil
179 }
180 return driver, config.DSN, nil
181
182 default:
183 return "", "", fmt.Errorf("unknown connection type: %s", config.ConnectionType)
184 }
185 }
186
187 // detectDriverFromDSN attempts to determine the driver from DSN format
188 func detectDriverFromDSN(dsn string) (driver, cleanDSN string) {
189 // Check for DB2 format
190 if containsDB2Keywords(dsn) {
191 return "go_ibm_db", dsn
192 }
193
194 // Check for ODBC format
195 if containsODBCKeywords(dsn) {
196 return "odbc", dsn
197 }
198
199 // Unknown format
200 return "", dsn
201 }