grokify/mogo

View on GitHub
database/datasource/datasource.go

Summary

Maintainability
A
0 mins
Test Coverage
package datasource

import (
    "database/sql"
    "errors"
    "net/url"
    "strconv"
    "strings"

    "github.com/grokify/mogo/errors/errorsutil"
    "github.com/grokify/mogo/net/netutil"
)

const (
    DriverBigQuery             = "bigquery"
    DriverGodror               = "godror"
    DriverMySQL                = "mysql"
    DriverOracle               = "oracle"
    DriverPostgres             = "postgres"
    DriverSQLite3              = "sqlite3"
    SchemePostgres             = "postgres"
    DefaultPortMySQL    uint16 = 3306
    DefaultPortPostgres uint16 = 5432
)

type DataSource struct {
    Driver   string              `json:"driver"`
    DSN      string              `json:"dsn"`
    Hostname string              `json:"hostname"` // does not include port
    Port     uint16              `json:"port"`     // 0-65535
    User     string              `json:"user,omitempty"`
    Password string              `json:"password,omitempty"`
    Database string              `json:"database,omitempty"`
    Query    map[string][]string `json:"query,omitempty"`
}

func (ds DataSource) Open() (*sql.DB, error) {
    dsn, err := ds.Name()
    if err != nil {
        return nil, err
    }
    return sql.Open(ds.Driver, dsn)
}

var (
    ErrDriverNameEmpty    = errors.New("driver name cannot be empty")
    ErrDriverNotSupported = errors.New("driver not supported")
)

// Name produces a URI DSN connection string
func (ds *DataSource) Name() (string, error) {
    ds.trim()
    if ds.DSN != "" {
        return ds.DSN, nil
    }
    switch ds.Driver {
    case DriverBigQuery:
        return dsnBigQuery(ds)
    // case DriverGodror:
    //    return dsnGodror(ds)
    case DriverMySQL:
        return dsnMySQL(ds), nil
    case DriverOracle:
        return dsnOracle(ds), nil
    case DriverPostgres:
        return dsnPostgres(ds)
    case DriverSQLite3:
        return dsnSQLite3(ds), nil
    case "":
        return "", ErrDriverNameEmpty
    default:
        return "", errorsutil.Wrapf(ErrDriverNotSupported, "driver name (%s)", ds.Driver)
    }
}

func (ds DataSource) Host() string {
    if port := ds.PortOrDefault(); port > 0 {
        return ds.HostnameOrDefault() + ":" + strconv.Itoa(int(port))
    }
    return strings.TrimSpace(ds.HostnameOrDefault())
}

func (ds DataSource) HostDatabase(sep string) string {
    parts := []string{}
    if host := strings.TrimSpace(ds.Host()); host != "" {
        parts = append(parts, host)
    }
    if db := strings.TrimSpace(ds.Database); db != "" {
        parts = append(parts, db)
    }
    return strings.TrimSpace(strings.Join(parts, sep))
}

// HostDatabaseQuery can be used when there's no need to validate query params
func (ds DataSource) HostDatabaseQuery(sep string) string {
    dsn := ds.HostDatabase(sep)
    if len(ds.Query) > 0 {
        qry := strings.TrimSpace(url.Values(ds.Query).Encode())
        if qry != "" {
            dsn += "?" + qry
        }
    }
    return dsn
}

func (ds DataSource) UserPassHostDatabase() string {
    dsn := ds.HostDatabase("/")
    if un := strings.TrimSpace(ds.UserPassword()); un != "" {
        dsn = un + "@" + dsn
    }
    return dsn
}

// UserPassHostDatabaseQuery can be used when there's no need to validate query params
func (ds DataSource) UserPassHostDatabaseQuery() string {
    dsn := ds.UserPassHostDatabase()
    if len(ds.Query) > 0 {
        qry := strings.TrimSpace(url.Values(ds.Query).Encode())
        if qry != "" {
            dsn += "?" + qry
        }
    }
    return dsn
}

func (ds DataSource) UserPassword() string {
    if ds.User == "" && ds.Password == "" {
        return ""
    } else if ds.Password == "" {
        return ds.User
    }
    return ds.User + ":" + ds.Password
}

func (ds DataSource) HostnameOrDefault() string {
    if strings.TrimSpace(ds.Hostname) == "" {
        return netutil.HostLocalhost
    }
    return strings.TrimSpace(ds.Hostname)
}

func (ds DataSource) PortOrDefault() uint16 {
    if ds.Port == 0 {
        switch ds.Driver {
        case DriverMySQL:
            return DefaultPortMySQL
        case DriverPostgres:
            return DefaultPortPostgres
        }
    }
    return ds.Port
}

func (ds *DataSource) trim() {
    ds.Driver = strings.ToLower(strings.TrimSpace(ds.Driver))
    ds.DSN = strings.TrimSpace(ds.DSN)
    ds.Hostname = strings.TrimSpace(ds.Hostname)
    ds.User = strings.TrimSpace(ds.User)
    ds.Password = strings.TrimSpace(ds.Password)
    ds.Database = strings.TrimSpace(ds.Database)
}