package db import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" "io/ioutil" "log" "os" "strings" ) const ( dbCredsEnvName = "DB_CREDS" dbCredsHostName = "DB_HOST" dsnTemplate = "dtookey:%s@tcp(%s)/%s?parseTime=true" ClarityDatabaseName = "projects" InsightDatabaseName = "insight" MercuryDatabaseName = "mercury" ) // /*====================================================================================== ConnectorGeneric ======================================================================================*/ type Blocker interface { ToQueryBlock() string } type ConnectorGeneric struct { cachedConnection *sql.DB } type SqlScriptRunner struct { ScriptName string DatabaseName string } func (c *ConnectorGeneric) ExecuteSqlScript(runner *SqlScriptRunner) { c.startConnection(runner.DatabaseName) defer c.returnConnection() queryWhole := *loadSqlFile(runner.ScriptName) queryParts := strings.Split(queryWhole, ";") log.Printf("\n=============================================\n%s\n==============================================\n", queryWhole) for _, query := range queryParts { if len(query) == 0 || query == "\n" { continue } _, err := c.cachedConnection.Exec(query + ";") if err != nil { log.Printf("Query involved in error: %s\n", query) log.Panic(err) } } } func (c *ConnectorGeneric) ExecuteString(dbName string, payload string) { c.startConnection(dbName) defer c.returnConnection() //log.Println("==================================================================================================") //log.Println(payload) //log.Println("==================================================================================================") _, err := c.cachedConnection.Exec(payload) if err != nil { log.Printf("Query involved in error: %s\n", payload) log.Panic(err) } } // startConnection this initializes, caches, and returns a connection for the desired database. If a connection already exists, // it will be terminated and a new connection will be opened. func (c *ConnectorGeneric) startConnection(dataBase string) { if c.cachedConnection != nil { c.returnConnection() } db := createDbConnection(dataBase) c.cachedConnection = db } // returnConnection This just closes the cached db connection func (c *ConnectorGeneric) returnConnection() { err := c.cachedConnection.Close() if err != nil { log.Panic(err) } c.cachedConnection = nil } func (c *ConnectorGeneric) QueryFromScript(scriptName string) *sql.Rows { query := loadSqlFile(scriptName) rows, err := c.cachedConnection.Query(*query) if err != nil { log.Panic(err) } return rows } func (c *ConnectorGeneric) ProcessClarityScripts() { //@dream standardize these script names tableCreationRunners := []*SqlScriptRunner{ NewRunner("0-run-first/1-sanitize_init.sql", ClarityDatabaseName), NewRunner("0-run-first/all_projects.sql", ClarityDatabaseName), NewRunner("0-run-first/billing.sql", ClarityDatabaseName), NewRunner("0-run-first/contributions.sql", ClarityDatabaseName), NewRunner("0-run-first/durations.sql", ClarityDatabaseName), NewRunner("0-run-first/lifecycle.sql", ClarityDatabaseName), } for _, runner := range tableCreationRunners { c.ExecuteSqlScript(runner) } } func (c *ConnectorGeneric) CreateTables() { tableCreationRunners := []*SqlScriptRunner{ NewRunner("create-any-database.sql", ""), NewRunner("create-insight-user-table.sql", InsightDatabaseName), NewRunner("create-insight-timeEntry-table.sql", InsightDatabaseName), NewRunner("create-mercury-picturePatterns-table.sql", MercuryDatabaseName), NewRunner("create-mercury-arReports-table.sql", MercuryDatabaseName), NewRunner("create-mercury-telecomVoice-table.sql", MercuryDatabaseName), NewRunner("create-mercury-failedReview-view.sql", MercuryDatabaseName), } for _, runner := range tableCreationRunners { c.ExecuteSqlScript(runner) } } // // /*====================================================================================== Utility Functions ======================================================================================*/ func createDbConnection(database string) *sql.DB { cred := os.Getenv(dbCredsEnvName) host := os.Getenv(dbCredsHostName) dbString := dsnTemplate connectString := fmt.Sprintf(dbString, cred, host, database) fmt.Printf("Beginning connection to database\n") db, err := sql.Open("mysql", connectString) if err != nil { log.Panic(err) } return db } func loadSqlFile(scriptName string) *string { file, err := os.OpenFile("src/sql/"+scriptName, os.O_RDONLY, 0755) if err != nil { log.Panic(err) } defer file.Close() raw, err := ioutil.ReadAll(file) if err != nil { log.Panic(err) } str := strings.Trim(string(raw), "\n") return &str } func QueryForObjects[K any](db *ConnectorGeneric, dbName string, queryScript string, mapping func(rows *sql.Rows) *K) *[]*K { rs := make([]*K, 0, 10000) db.startConnection(dbName) defer db.returnConnection() res := db.QueryFromScript(queryScript) for res.Next() { o := mapping(res) rs = append(rs, o) } return &rs } func BulkUpdate[K any](connector *ConnectorGeneric, dbName string, updateScript string, items *[]*K, mapping func(s *sql.Stmt, item *K)) { connector.startConnection(dbName) defer connector.returnConnection() statement := loadSqlFile(updateScript) s, err := connector.cachedConnection.Prepare(*statement) if err != nil { log.Panic(err) } for _, item := range *items { mapping(s, item) } } func BlockUpdate[K Blocker](connector *ConnectorGeneric, dbName string, updateScript string, items *[]*K) { template := loadSqlFile(updateScript) buff := strings.Builder{} l := len(*items) for i, item := range *items { buff.WriteString((*item).ToQueryBlock()) if i < l-1 { buff.WriteByte(',') } } query := fmt.Sprintf(*template, buff.String()) connector.ExecuteString(dbName, query) } func NewRunner(scriptName string, databaseName string) *SqlScriptRunner { return &SqlScriptRunner{scriptName, databaseName} } //