package services
import (
"fmt"
"strings"
"sync"
"time"
)
const (
MaxConnections = 100
ConnectionTimeout = 30 * time.Second
QueryTimeout = 60 * time.Second
DefaultPort = 5432
)
type Database interface {
Connect() error
Close() error
Execute(query string, args []interface{}) error
Query(query string, args []interface{}) (*QueryResult, error)
BeginTransaction() (Transaction, error)
IsConnected() bool
}
type DatabaseConnection struct {
connectionURL string
connected bool
mockData map[string]interface{}
mutex sync.RWMutex
connectionPool *ConnectionPool
}
type ConnectionPool struct {
maxConnections int
activeConnections int
mutex sync.Mutex
}
func NewConnectionPool(maxConnections int) *ConnectionPool {
return &ConnectionPool{
maxConnections: maxConnections,
activeConnections: 0,
}
}
func (cp *ConnectionPool) Acquire() error {
cp.mutex.Lock()
defer cp.mutex.Unlock()
if cp.activeConnections >= cp.maxConnections {
return NewDatabaseError(ErrConnectionPoolFull, "connection pool is full")
}
cp.activeConnections++
return nil
}
func (cp *ConnectionPool) Release() {
cp.mutex.Lock()
defer cp.mutex.Unlock()
if cp.activeConnections > 0 {
cp.activeConnections--
}
}
func (cp *ConnectionPool) Stats() ConnectionPoolStats {
cp.mutex.Lock()
defer cp.mutex.Unlock()
return ConnectionPoolStats{
MaxConnections: cp.maxConnections,
ActiveConnections: cp.activeConnections,
AvailableConnections: cp.maxConnections - cp.activeConnections,
}
}
type ConnectionPoolStats struct {
MaxConnections int
ActiveConnections int
AvailableConnections int
}
func NewDatabaseConnection(connectionURL string) *DatabaseConnection {
return &DatabaseConnection{
connectionURL: connectionURL,
connected: false,
mockData: make(map[string]interface{}),
connectionPool: NewConnectionPool(MaxConnections),
}
}
func (db *DatabaseConnection) Connect() error {
if db.connected {
return nil
}
if db.connectionURL == "" {
return NewDatabaseError(ErrInvalidConnectionString, "connection URL cannot be empty")
}
if !strings.Contains(db.connectionURL, "://") {
return NewDatabaseError(ErrInvalidConnectionString, "invalid connection URL format")
}
if err := db.connectionPool.Acquire(); err != nil {
return err
}
fmt.Printf("Connecting to database: %s\n", db.connectionURL)
db.mutex.Lock()
db.connected = true
db.mutex.Unlock()
return nil
}
func (db *DatabaseConnection) Close() error {
db.mutex.Lock()
defer db.mutex.Unlock()
if !db.connected {
return nil
}
db.connected = false
db.mockData = make(map[string]interface{})
db.connectionPool.Release()
fmt.Println("Database connection closed")
return nil
}
func (db *DatabaseConnection) Execute(query string, args []interface{}) error {
if err := db.checkConnection(); err != nil {
return err
}
if strings.TrimSpace(query) == "" {
return NewDatabaseError(ErrInvalidQuery, "query cannot be empty")
}
fmt.Printf("Executing query: %s with %d args\n", query, len(args))
queryUpper := strings.ToUpper(strings.TrimSpace(query))
switch {
case strings.HasPrefix(queryUpper, "INSERT"):
return db.mockInsert(query, args)
case strings.HasPrefix(queryUpper, "UPDATE"):
return db.mockUpdate(query, args)
case strings.HasPrefix(queryUpper, "DELETE"):
return db.mockDelete(query, args)
default:
return NewDatabaseError(ErrUnsupportedOperation, "execute only supports INSERT, UPDATE, DELETE")
}
}
func (db *DatabaseConnection) Query(query string, args []interface{}) (*QueryResult, error) {
if err := db.checkConnection(); err != nil {
return nil, err
}
if strings.TrimSpace(query) == "" {
return nil, NewDatabaseError(ErrInvalidQuery, "query cannot be empty")
}
fmt.Printf("Querying: %s with %d args\n", query, len(args))
queryUpper := strings.ToUpper(strings.TrimSpace(query))
if strings.HasPrefix(queryUpper, "SELECT") {
return db.mockSelect(query, args)
}
return nil, NewDatabaseError(ErrUnsupportedOperation, "query only supports SELECT statements")
}
func (db *DatabaseConnection) BeginTransaction() (Transaction, error) {
if err := db.checkConnection(); err != nil {
return nil, err
}
return NewDatabaseTransaction(db), nil
}
func (db *DatabaseConnection) IsConnected() bool {
db.mutex.RLock()
defer db.mutex.RUnlock()
return db.connected
}
func (db *DatabaseConnection) GetConnectionURL() string {
return db.connectionURL
}
func (db *DatabaseConnection) GetPoolStats() ConnectionPoolStats {
return db.connectionPool.Stats()
}
func (db *DatabaseConnection) checkConnection() error {
db.mutex.RLock()
defer db.mutex.RUnlock()
if !db.connected {
return NewDatabaseError(ErrNotConnected, "database not connected")
}
return nil
}
func (db *DatabaseConnection) mockInsert(query string, args []interface{}) error {
db.mutex.Lock()
defer db.mutex.Unlock()
key := fmt.Sprintf("insert_%d", len(db.mockData))
db.mockData[key] = args
return nil
}
func (db *DatabaseConnection) mockUpdate(query string, args []interface{}) error {
return nil
}
func (db *DatabaseConnection) mockDelete(query string, args []interface{}) error {
return nil
}
func (db *DatabaseConnection) mockSelect(query string, args []interface{}) (*QueryResult, error) {
rows := []map[string]interface{}{
{"id": 1, "name": "Test User", "email": "test@example.com"},
{"id": 2, "name": "Another User", "email": "another@example.com"},
}
return &QueryResult{
Rows: rows,
RowsAffected: 0,
LastInsertID: nil,
}, nil
}
type Transaction interface {
Commit() error
Rollback() error
Execute(query string, args []interface{}) error
Query(query string, args []interface{}) (*QueryResult, error)
}
type DatabaseTransaction struct {
db *DatabaseConnection
committed bool
rolledBack bool
}
func NewDatabaseTransaction(db *DatabaseConnection) *DatabaseTransaction {
return &DatabaseTransaction{
db: db,
}
}
func (tx *DatabaseTransaction) Commit() error {
if tx.committed || tx.rolledBack {
return NewDatabaseError(ErrTransactionClosed, "transaction already closed")
}
tx.committed = true
fmt.Println("Transaction committed")
return nil
}
func (tx *DatabaseTransaction) Rollback() error {
if tx.committed || tx.rolledBack {
return NewDatabaseError(ErrTransactionClosed, "transaction already closed")
}
tx.rolledBack = true
fmt.Println("Transaction rolled back")
return nil
}
func (tx *DatabaseTransaction) Execute(query string, args []interface{}) error {
if tx.committed || tx.rolledBack {
return NewDatabaseError(ErrTransactionClosed, "transaction is closed")
}
return tx.db.Execute(query, args)
}
func (tx *DatabaseTransaction) Query(query string, args []interface{}) (*QueryResult, error) {
if tx.committed || tx.rolledBack {
return nil, NewDatabaseError(ErrTransactionClosed, "transaction is closed")
}
return tx.db.Query(query, args)
}
type QueryResult struct {
Rows []map[string]interface{}
RowsAffected int64
LastInsertID *int64
}
func (qr *QueryResult) HasRows() bool {
return len(qr.Rows) > 0
}
func (qr *QueryResult) RowCount() int {
return len(qr.Rows)
}
func ValidateConnectionString(connectionString string) error {
if connectionString == "" {
return NewDatabaseError(ErrInvalidConnectionString, "connection string cannot be empty")
}
if !strings.Contains(connectionString, "://") {
return NewDatabaseError(ErrInvalidConnectionString, "connection string must contain protocol")
}
return nil
}
func EscapeSQLIdentifier(identifier string) string {
return fmt.Sprintf("`%s`", strings.ReplaceAll(identifier, "`", "``"))
}
func EscapeSQLString(value string) string {
return fmt.Sprintf("'%s'", strings.ReplaceAll(value, "'", "''"))
}
func ParseConnectionString(connectionString string) (ConnectionInfo, error) {
if err := ValidateConnectionString(connectionString); err != nil {
return ConnectionInfo{}, err
}
parts := strings.Split(connectionString, "://")
if len(parts) != 2 {
return ConnectionInfo{}, NewDatabaseError(ErrInvalidConnectionString, "invalid format")
}
return ConnectionInfo{
Protocol: parts[0],
Address: parts[1],
}, nil
}
type ConnectionInfo struct {
Protocol string
Address string
Host string
Port int
Database string
}
type DatabaseErrorCode int
const (
ErrNotConnected DatabaseErrorCode = iota
ErrInvalidConnectionString
ErrInvalidQuery
ErrConnectionPoolFull
ErrTransactionClosed
ErrUnsupportedOperation
ErrQueryTimeout
)
type DatabaseError struct {
Code DatabaseErrorCode
Message string
}
func NewDatabaseError(code DatabaseErrorCode, message string) *DatabaseError {
return &DatabaseError{
Code: code,
Message: message,
}
}
func (e *DatabaseError) Error() string {
return fmt.Sprintf("database error [%d]: %s", int(e.Code), e.Message)
}
func (e *DatabaseError) Is(target error) bool {
if other, ok := target.(*DatabaseError); ok {
return e.Code == other.Code
}
return false
}
func (e *DatabaseError) Temporary() bool {
return e.Code == ErrConnectionPoolFull || e.Code == ErrQueryTimeout
}
func init() {
fmt.Println("[INIT] Database service package initialized")
}