2020-09-30 07:56:23 +00:00
|
|
|
package db
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
2020-10-11 00:03:16 +00:00
|
|
|
"fmt"
|
2020-09-30 07:56:23 +00:00
|
|
|
"io/ioutil"
|
2020-10-11 00:03:16 +00:00
|
|
|
"regexp"
|
|
|
|
"sort"
|
2020-09-30 07:56:23 +00:00
|
|
|
"time"
|
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
"git.example.com/example/goserv/assets/configfs"
|
2020-09-30 07:56:23 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
|
|
|
|
// pq injects itself into sql as 'postgres'
|
|
|
|
_ "github.com/lib/pq"
|
|
|
|
)
|
|
|
|
|
|
|
|
// DB is a concurrency-safe db connection instance
|
|
|
|
var DB *sqlx.DB
|
2020-10-11 00:03:16 +00:00
|
|
|
var firstDBURL PleaseDoubleCheckTheDatabaseURLDontDropProd
|
2020-09-30 07:56:23 +00:00
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
// Init initializes the database
|
2020-09-30 07:56:23 +00:00
|
|
|
func Init(pgURL string) error {
|
|
|
|
// https://godoc.org/github.com/lib/pq
|
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
firstDBURL = PleaseDoubleCheckTheDatabaseURLDontDropProd(pgURL)
|
|
|
|
dbtype := "postgres"
|
|
|
|
|
|
|
|
ctx, done := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
|
|
|
|
defer done()
|
|
|
|
db, err := sql.Open(dbtype, pgURL)
|
|
|
|
if err := db.PingContext(ctx); nil != err {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// basic stuff
|
2020-09-30 07:56:23 +00:00
|
|
|
f, err := configfs.Assets.Open("./postgres/init.sql")
|
|
|
|
if nil != err {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
sqlBytes, err := ioutil.ReadAll(f)
|
|
|
|
if nil != err {
|
|
|
|
return err
|
|
|
|
}
|
2020-10-11 00:03:16 +00:00
|
|
|
if _, err := db.ExecContext(ctx, string(sqlBytes)); nil != err {
|
|
|
|
return err
|
|
|
|
}
|
2020-09-30 07:56:23 +00:00
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
// project-specific stuff
|
|
|
|
f, err = configfs.Assets.Open("./postgres/tables.sql")
|
|
|
|
if nil != err {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
sqlBytes, err = ioutil.ReadAll(f)
|
|
|
|
if nil != err {
|
2020-09-30 07:56:23 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := db.ExecContext(ctx, string(sqlBytes)); nil != err {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
DB = sqlx.NewDb(db, dbtype)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-10-11 00:03:16 +00:00
|
|
|
|
|
|
|
// PleaseDoubleCheckTheDatabaseURLDontDropProd is just a friendly,
|
|
|
|
// hopefully helpful reminder, not to only use this in test files,
|
|
|
|
// and to not drop the production database
|
|
|
|
type PleaseDoubleCheckTheDatabaseURLDontDropProd string
|
|
|
|
|
|
|
|
// DropAllTables runs drop.sql, which is intended only for tests
|
|
|
|
func DropAllTables(dbURL PleaseDoubleCheckTheDatabaseURLDontDropProd) error {
|
|
|
|
if err := CanDropAllTables(string(dbURL)); nil != err {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// drop stuff
|
|
|
|
f, err := configfs.Assets.Open("./postgres/drop.sql")
|
|
|
|
if nil != err {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
sqlBytes, err := ioutil.ReadAll(f)
|
|
|
|
if nil != err {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ctx, done := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second))
|
|
|
|
defer done()
|
|
|
|
if _, err := DB.ExecContext(ctx, string(sqlBytes)); nil != err {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CanDropAllTables returns an error if the dbURL does not contain the words "test" or
|
|
|
|
// "demo" at a letter boundary
|
|
|
|
func CanDropAllTables(dbURL string) error {
|
|
|
|
var isDemo bool
|
|
|
|
nonalpha := regexp.MustCompile(`[^a-zA-Z]`)
|
|
|
|
haystack := nonalpha.Split(dbURL, -1)
|
|
|
|
sort.Strings(haystack)
|
|
|
|
for _, needle := range []string{"test", "demo"} {
|
|
|
|
// the index to insert x if x is not present (it could be len(a))
|
|
|
|
// (meaning that it is the index at which it exists, if it exists)
|
|
|
|
i := sort.SearchStrings(haystack, needle)
|
|
|
|
if i < len(haystack) && haystack[i] == needle {
|
|
|
|
isDemo = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if isDemo {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return fmt.Errorf(
|
|
|
|
"test and demo database URLs must contain the word 'test' or 'demo' "+
|
|
|
|
"separated by a non-alphabet character, such as /test2/db_demo1\n%q\n",
|
|
|
|
dbURL,
|
|
|
|
)
|
|
|
|
}
|