2019-02-14 13:33:42 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package pgutil
|
|
|
|
|
|
|
|
import (
|
2020-01-13 13:18:48 +00:00
|
|
|
"context"
|
2019-03-12 13:29:13 +00:00
|
|
|
"strings"
|
2019-02-14 13:33:42 +00:00
|
|
|
|
2019-04-04 15:42:01 +01:00
|
|
|
"github.com/lib/pq"
|
2019-11-08 20:40:39 +00:00
|
|
|
"github.com/spacemonkeygo/monkit/v3"
|
2019-02-14 13:33:42 +00:00
|
|
|
"github.com/zeebo/errs"
|
|
|
|
|
2019-11-14 19:46:15 +00:00
|
|
|
"storj.io/storj/private/dbutil"
|
|
|
|
"storj.io/storj/private/dbutil/dbschema"
|
2020-01-19 14:41:23 +00:00
|
|
|
"storj.io/storj/private/tagsql"
|
2019-02-14 13:33:42 +00:00
|
|
|
)
|
|
|
|
|
2019-06-04 22:30:21 +01:00
|
|
|
var (
|
|
|
|
mon = monkit.Package()
|
|
|
|
)
|
|
|
|
|
2019-12-04 03:36:21 +00:00
|
|
|
// OpenUnique opens a postgres database with a temporary unique schema, which will be cleaned up
|
|
|
|
// when closed. It is expected that this should normally be used by way of
|
|
|
|
// "storj.io/storj/private/dbutil/tempdb".OpenUnique() instead of calling it directly.
|
2020-01-13 13:18:48 +00:00
|
|
|
func OpenUnique(ctx context.Context, connstr string, schemaPrefix string) (*dbutil.TempDatabase, error) {
|
2019-12-04 03:36:21 +00:00
|
|
|
// sanity check, because you get an unhelpful error message when this happens
|
|
|
|
if strings.HasPrefix(connstr, "cockroach://") {
|
|
|
|
return nil, errs.New("can't connect to cockroach using pgutil.OpenUnique()! connstr=%q. try tempdb.OpenUnique() instead?", connstr)
|
2019-02-14 13:33:42 +00:00
|
|
|
}
|
|
|
|
|
2019-12-04 03:36:21 +00:00
|
|
|
schemaName := schemaPrefix + "-" + CreateRandomTestingSchemaName(8)
|
|
|
|
connStrWithSchema := ConnstrWithSchema(connstr, schemaName)
|
2019-02-14 13:33:42 +00:00
|
|
|
|
2020-01-19 14:41:23 +00:00
|
|
|
db, err := tagsql.Open("postgres", connStrWithSchema)
|
2019-12-04 03:36:21 +00:00
|
|
|
if err == nil {
|
|
|
|
// check that connection actually worked before trying CreateSchema, to make
|
|
|
|
// troubleshooting (lots) easier
|
2020-01-19 14:41:23 +00:00
|
|
|
err = db.PingContext(ctx)
|
2019-02-14 13:33:42 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
2020-01-14 11:41:23 +00:00
|
|
|
return nil, errs.New("failed to connect to %q with driver postgres: %w", connStrWithSchema, err)
|
2019-02-14 13:33:42 +00:00
|
|
|
}
|
|
|
|
|
2020-01-13 13:18:48 +00:00
|
|
|
err = CreateSchema(ctx, db, schemaName)
|
2019-02-14 13:33:42 +00:00
|
|
|
if err != nil {
|
2019-12-04 03:36:21 +00:00
|
|
|
return nil, errs.Combine(err, db.Close())
|
2019-02-14 13:33:42 +00:00
|
|
|
}
|
|
|
|
|
2020-01-19 14:41:23 +00:00
|
|
|
cleanup := func(cleanupDB tagsql.DB) error {
|
2020-01-13 13:18:48 +00:00
|
|
|
return DropSchema(ctx, cleanupDB, schemaName)
|
2019-02-14 13:33:42 +00:00
|
|
|
}
|
|
|
|
|
2020-01-15 07:25:26 +00:00
|
|
|
dbutil.Configure(db, mon)
|
2019-12-04 03:36:21 +00:00
|
|
|
return &dbutil.TempDatabase{
|
|
|
|
DB: db,
|
|
|
|
ConnStr: connStrWithSchema,
|
|
|
|
Schema: schemaName,
|
|
|
|
Driver: "postgres",
|
|
|
|
Implementation: dbutil.Postgres,
|
|
|
|
Cleanup: cleanup,
|
|
|
|
}, nil
|
2019-02-14 13:33:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// QuerySnapshot loads snapshot from database
|
2020-01-13 13:18:48 +00:00
|
|
|
func QuerySnapshot(ctx context.Context, db dbschema.Queryer) (*dbschema.Snapshot, error) {
|
|
|
|
schema, err := QuerySchema(ctx, db)
|
2019-02-14 13:33:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-13 13:18:48 +00:00
|
|
|
data, err := QueryData(ctx, db, schema)
|
2019-02-14 13:33:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &dbschema.Snapshot{
|
|
|
|
Version: -1,
|
|
|
|
Schema: schema,
|
|
|
|
Data: data,
|
|
|
|
}, err
|
|
|
|
}
|
2019-03-12 13:29:13 +00:00
|
|
|
|
2019-12-04 03:36:21 +00:00
|
|
|
// CheckApplicationName ensures that the Connection String contains an application name
|
2019-03-12 13:29:13 +00:00
|
|
|
func CheckApplicationName(s string) (r string) {
|
|
|
|
if !strings.Contains(s, "application_name") {
|
|
|
|
if !strings.Contains(s, "?") {
|
|
|
|
r = s + "?application_name=Satellite"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r = s + "&application_name=Satellite"
|
|
|
|
return
|
|
|
|
}
|
2019-12-04 03:36:21 +00:00
|
|
|
// return source as is if application_name is set
|
2019-03-12 13:29:13 +00:00
|
|
|
return s
|
|
|
|
}
|
2019-04-04 15:42:01 +01:00
|
|
|
|
|
|
|
// IsConstraintError checks if given error is about constraint violation
|
|
|
|
func IsConstraintError(err error) bool {
|
2019-06-26 08:38:07 +01:00
|
|
|
return errs.IsFunc(err, func(err error) bool {
|
2019-04-23 12:13:57 +01:00
|
|
|
if e, ok := err.(*pq.Error); ok {
|
|
|
|
if e.Code.Class() == "23" {
|
|
|
|
return true
|
|
|
|
}
|
2019-04-04 15:42:01 +01:00
|
|
|
}
|
2019-04-23 12:13:57 +01:00
|
|
|
return false
|
|
|
|
})
|
2019-04-04 15:42:01 +01:00
|
|
|
}
|