🐹 Database Integration in TuskLang for Go

Go Documentation

Database Integration in TuskLang for Go

Overview

Database integration in TuskLang provides powerful database configuration and query capabilities directly in your configuration files. These features enable you to define sophisticated database connections, query patterns, and data access strategies with Go integration for robust data-driven applications.

Basic Database Configuration

// TuskLang database configuration
database: {
    connections: {
        primary: {
            type: "postgresql"
            host: "@env('DB_HOST', 'localhost')"
            port: "@env('DB_PORT', '5432')"
            database: "@env('DB_NAME', 'myapp')"
            username: "@env('DB_USER', 'postgres')"
            password: "@env('DB_PASSWORD')"
            ssl_mode: "require"
            max_connections: 20
            idle_timeout: "5m"
        }
        
        read_replica: {
            type: "postgresql"
            host: "@env('DB_READ_HOST', 'localhost')"
            port: "@env('DB_READ_PORT', '5432')"
            database: "@env('DB_NAME', 'myapp')"
            username: "@env('DB_USER', 'postgres')"
            password: "@env('DB_PASSWORD')"
            ssl_mode: "require"
            max_connections: 10
            read_only: true
        }
        
        cache: {
            type: "redis"
            host: "@env('REDIS_HOST', 'localhost')"
            port: "@env('REDIS_PORT', '6379')"
            database: 0
            password: "@env('REDIS_PASSWORD')"
            pool_size: 10
        }
    }
    
    queries: {
        user_by_id: "SELECT id, name, email, created_at FROM users WHERE id = $1"
        users_by_role: "SELECT id, name, email FROM users WHERE role = $1 ORDER BY created_at DESC"
        user_count: "SELECT COUNT(*) FROM users WHERE active = true"
        create_user: "INSERT INTO users (name, email, role) VALUES ($1, $2, $3) RETURNING id"
        update_user: "UPDATE users SET name = $2, email = $3 WHERE id = $1"
        delete_user: "DELETE FROM users WHERE id = $1"
    }
    
    migrations: {
        enabled: true
        path: "./migrations"
        table: "schema_migrations"
        timeout: "5m"
    }
    
    pooling: {
        enabled: true
        max_open_connections: 25
        max_idle_connections: 5
        connection_max_lifetime: "1h"
        connection_max_idle_time: "15m"
    }
}

Go Integration

package main

import ( "context" "database/sql" "fmt" "log" "time" _ "github.com/lib/pq" "github.com/go-redis/redis/v8" "github.com/tusklang/go-sdk" )

type DatabaseConfig struct { Connections map[string]Connection tsk:"connections" Queries map[string]string tsk:"queries" Migrations MigrationConfig tsk:"migrations" Pooling PoolingConfig tsk:"pooling" }

type Connection struct { Type string tsk:"type" Host string tsk:"host" Port string tsk:"port" Database string tsk:"database" Username string tsk:"username" Password string tsk:"password" SSLMode string tsk:"ssl_mode" MaxConnections int tsk:"max_connections" IdleTimeout string tsk:"idle_timeout" ReadOnly bool tsk:"read_only" PoolSize int tsk:"pool_size" }

type MigrationConfig struct { Enabled bool tsk:"enabled" Path string tsk:"path" Table string tsk:"table" Timeout string tsk:"timeout" }

type PoolingConfig struct { Enabled bool tsk:"enabled" MaxOpenConnections int tsk:"max_open_connections" MaxIdleConnections int tsk:"max_idle_connections" ConnectionMaxLifetime string tsk:"connection_max_lifetime" ConnectionMaxIdleTime string tsk:"connection_max_idle_time" }

type DatabaseManager struct { config DatabaseConfig primary *sql.DB readReplica *sql.DB cache *redis.Client queries map[string]string }

type User struct { ID int json:"id" Name string json:"name" Email string json:"email" Role string json:"role" Active bool json:"active" CreatedAt time.Time json:"created_at" }

func main() { // Load database configuration config, err := tusk.LoadFile("database-config.tsk") if err != nil { log.Fatalf("Error loading database config: %v", err) } var dbConfig DatabaseConfig if err := config.Get("database", &dbConfig); err != nil { log.Fatalf("Error parsing database config: %v", err) } // Initialize database manager dbManager := NewDatabaseManager(dbConfig) defer dbManager.Close() // Run migrations if err := dbManager.RunMigrations(); err != nil { log.Fatalf("Error running migrations: %v", err) } // Example usage user, err := dbManager.GetUserByID(1) if err != nil { log.Printf("Error getting user: %v", err) } else { log.Printf("User: %+v", user) } // Create new user newUser := User{ Name: "John Doe", Email: "john@example.com", Role: "user", } userID, err := dbManager.CreateUser(newUser) if err != nil { log.Printf("Error creating user: %v", err) } else { log.Printf("Created user with ID: %d", userID) } }

func NewDatabaseManager(config DatabaseConfig) *DatabaseManager { manager := &DatabaseManager{ config: config, queries: config.Queries, } // Initialize primary database connection if primary, exists := config.Connections["primary"]; exists { db, err := manager.connectPostgreSQL(primary) if err != nil { log.Fatalf("Error connecting to primary database: %v", err) } manager.primary = db } // Initialize read replica connection if readReplica, exists := config.Connections["read_replica"]; exists { db, err := manager.connectPostgreSQL(readReplica) if err != nil { log.Printf("Warning: Error connecting to read replica: %v", err) } else { manager.readReplica = db } } // Initialize cache connection if cache, exists := config.Connections["cache"]; exists { redisClient, err := manager.connectRedis(cache) if err != nil { log.Printf("Warning: Error connecting to cache: %v", err) } else { manager.cache = redisClient } } return manager }

func (dm DatabaseManager) connectPostgreSQL(conn Connection) (sql.DB, error) { dsn := fmt.Sprintf( "host=%s port=%s dbname=%s user=%s password=%s sslmode=%s", conn.Host, conn.Port, conn.Database, conn.Username, conn.Password, conn.SSLMode, ) db, err := sql.Open("postgres", dsn) if err != nil { return nil, err } // Configure connection pooling if dm.config.Pooling.Enabled { db.SetMaxOpenConns(dm.config.Pooling.MaxOpenConnections) db.SetMaxIdleConns(dm.config.Pooling.MaxIdleConnections) if lifetime, err := time.ParseDuration(dm.config.Pooling.ConnectionMaxLifetime); err == nil { db.SetConnMaxLifetime(lifetime) } if idleTime, err := time.ParseDuration(dm.config.Pooling.ConnectionMaxIdleTime); err == nil { db.SetConnMaxIdleTime(idleTime) } } // Test connection if err := db.Ping(); err != nil { return nil, err } return db, nil }

func (dm DatabaseManager) connectRedis(conn Connection) (redis.Client, error) { client := redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%s", conn.Host, conn.Port), Password: conn.Password, DB: conn.Database, PoolSize: conn.PoolSize, }) // Test connection ctx := context.Background() if err := client.Ping(ctx).Err(); err != nil { return nil, err } return client, nil }

// Database operations func (dm DatabaseManager) GetUserByID(id int) (User, error) { // Try cache first if dm.cache != nil { if user, err := dm.getUserFromCache(id); err == nil { return user, nil } } // Query database query := dm.queries["user_by_id"] row := dm.getReadDB().QueryRow(query, id) user := &User{} err := row.Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt) if err != nil { return nil, err } // Cache the result if dm.cache != nil { dm.cacheUser(user) } return user, nil }

func (dm DatabaseManager) GetUsersByRole(role string) ([]User, error) { query := dm.queries["users_by_role"] rows, err := dm.getReadDB().Query(query, role) if err != nil { return nil, err } defer rows.Close() var users []*User for rows.Next() { user := &User{} err := rows.Scan(&user.ID, &user.Name, &user.Email) if err != nil { return nil, err } users = append(users, user) } return users, nil }

func (dm *DatabaseManager) GetUserCount() (int, error) { query := dm.queries["user_count"] row := dm.getReadDB().QueryRow(query) var count int err := row.Scan(&count) return count, err }

func (dm *DatabaseManager) CreateUser(user User) (int, error) { query := dm.queries["create_user"] row := dm.primary.QueryRow(query, user.Name, user.Email, user.Role) var id int err := row.Scan(&id) if err != nil { return 0, err } // Invalidate cache if dm.cache != nil { dm.invalidateUserCache(id) } return id, nil }

func (dm *DatabaseManager) UpdateUser(id int, user User) error { query := dm.queries["update_user"] _, err := dm.primary.Exec(query, id, user.Name, user.Email) if err != nil { return err } // Invalidate cache if dm.cache != nil { dm.invalidateUserCache(id) } return nil }

func (dm *DatabaseManager) DeleteUser(id int) error { query := dm.queries["delete_user"] _, err := dm.primary.Exec(query, id) if err != nil { return err } // Invalidate cache if dm.cache != nil { dm.invalidateUserCache(id) } return nil }

func (dm DatabaseManager) getReadDB() sql.DB { if dm.readReplica != nil { return dm.readReplica } return dm.primary }

// Cache operations func (dm DatabaseManager) getUserFromCache(id int) (User, error) { ctx := context.Background() key := fmt.Sprintf("user:%d", id) data, err := dm.cache.Get(ctx, key).Result() if err != nil { return nil, err } var user User if err := json.Unmarshal([]byte(data), &user); err != nil { return nil, err } return &user, nil }

func (dm DatabaseManager) cacheUser(user User) { ctx := context.Background() key := fmt.Sprintf("user:%d", user.ID) data, err := json.Marshal(user) if err != nil { return } dm.cache.Set(ctx, key, data, time.Hour) }

func (dm *DatabaseManager) invalidateUserCache(id int) { ctx := context.Background() key := fmt.Sprintf("user:%d", id) dm.cache.Del(ctx, key) }

// Migration operations func (dm *DatabaseManager) RunMigrations() error { if !dm.config.Migrations.Enabled { return nil } // Create migrations table if it doesn't exist createTableSQL := fmt.Sprintf( CREATE TABLE IF NOT EXISTS %s ( id SERIAL PRIMARY KEY, version VARCHAR(255) NOT NULL UNIQUE, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) , dm.config.Migrations.Table) _, err := dm.primary.Exec(createTableSQL) if err != nil { return err } // Read migration files files, err := os.ReadDir(dm.config.Migrations.Path) if err != nil { return err } // Sort files by name var migrations []string for _, file := range files { if strings.HasSuffix(file.Name(), ".sql") { migrations = append(migrations, file.Name()) } } sort.Strings(migrations) // Apply migrations for _, migration := range migrations { if err := dm.applyMigration(migration); err != nil { return err } } return nil }

func (dm *DatabaseManager) applyMigration(filename string) error { // Check if migration already applied var count int query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE version = $1", dm.config.Migrations.Table) err := dm.primary.QueryRow(query, filename).Scan(&count) if err != nil { return err } if count > 0 { return nil // Already applied } // Read migration file filepath := filepath.Join(dm.config.Migrations.Path, filename) content, err := os.ReadFile(filepath) if err != nil { return err } // Start transaction tx, err := dm.primary.Begin() if err != nil { return err } defer tx.Rollback() // Execute migration _, err = tx.Exec(string(content)) if err != nil { return err } // Record migration recordQuery := fmt.Sprintf("INSERT INTO %s (version) VALUES ($1)", dm.config.Migrations.Table) _, err = tx.Exec(recordQuery, filename) if err != nil { return err } // Commit transaction return tx.Commit() }

func (dm *DatabaseManager) Close() error { if dm.primary != nil { dm.primary.Close() } if dm.readReplica != nil { dm.readReplica.Close() } if dm.cache != nil { dm.cache.Close() } return nil }

Advanced Database Features

Query Builder

// TuskLang configuration with query builder
database: {
    query_builder: {
        enabled: true
        features: {
            joins: true
            where_clauses: true
            order_by: true
            group_by: true
            having: true
            limit_offset: true
        }
        
        templates: {
            user_search: {
                base: "SELECT id, name, email FROM users"
                where: "WHERE active = true"
                order: "ORDER BY created_at DESC"
                limit: "LIMIT $1 OFFSET $2"
            }
            
            product_list: {
                base: "SELECT p.id, p.name, p.price, c.name as category FROM products p"
                join: "JOIN categories c ON p.category_id = c.id"
                where: "WHERE p.active = true"
                order: "ORDER BY p.created_at DESC"
            }
        }
    }
}

Connection Pooling

// TuskLang configuration with advanced pooling
database: {
    pooling: {
        enabled: true
        strategies: {
            round_robin: {
                enabled: true
                connections: ["primary", "read_replica_1", "read_replica_2"]
            }
            
            weighted: {
                enabled: true
                weights: {
                    primary: 1
                    read_replica_1: 2
                    read_replica_2: 2
                }
            }
        }
        
        health_checks: {
            enabled: true
            interval: "30s"
            timeout: "5s"
            max_failures: 3
        }
    }
}

Performance Considerations

- Connection Pooling: Use appropriate connection pool sizes - Query Optimization: Optimize queries for performance - Caching: Implement caching for frequently accessed data - Read Replicas: Use read replicas for read-heavy workloads - Indexing: Ensure proper database indexing

Security Notes

- Connection Security: Use SSL/TLS for database connections - Credential Management: Secure database credentials - SQL Injection: Use parameterized queries to prevent SQL injection - Access Control: Implement proper database access controls - Audit Logging: Log database operations for security auditing

Best Practices

1. Connection Management: Properly manage database connections 2. Error Handling: Implement comprehensive error handling 3. Transaction Management: Use transactions for data consistency 4. Migration Strategy: Use version-controlled database migrations 5. Monitoring: Monitor database performance and health 6. Backup Strategy: Implement proper database backup strategies

Integration Examples

With GORM

import (
    "gorm.io/gorm"
    "gorm.io/driver/postgres"
    "github.com/tusklang/go-sdk"
)

func setupGORM(config tusk.Config) *gorm.DB { var dbConfig DatabaseConfig config.Get("database", &dbConfig) primary := dbConfig.Connections["primary"] dsn := fmt.Sprintf( "host=%s port=%s dbname=%s user=%s password=%s sslmode=%s", primary.Host, primary.Port, primary.Database, primary.Username, primary.Password, primary.SSLMode, ) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { log.Fatalf("Error connecting to database: %v", err) } return db }

With SQLx

import (
    "github.com/jmoiron/sqlx"
    _ "github.com/lib/pq"
    "github.com/tusklang/go-sdk"
)

func setupSQLx(config tusk.Config) *sqlx.DB { var dbConfig DatabaseConfig config.Get("database", &dbConfig) primary := dbConfig.Connections["primary"] dsn := fmt.Sprintf( "host=%s port=%s dbname=%s user=%s password=%s sslmode=%s", primary.Host, primary.Port, primary.Database, primary.Username, primary.Password, primary.SSLMode, ) db, err := sqlx.Connect("postgres", dsn) if err != nil { log.Fatalf("Error connecting to database: %v", err) } return db }

This comprehensive database integration documentation provides Go developers with everything they need to build sophisticated database systems using TuskLang's powerful configuration capabilities.