Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
151 lines
3.1 KiB
Go
151 lines
3.1 KiB
Go
package dbmanager
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"hash/fnv"
|
|
"io"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"golang.org/x/sync/singleflight"
|
|
|
|
"github.com/sqlc-dev/sqlc/internal/config"
|
|
"github.com/sqlc-dev/sqlc/internal/pgx/poolcache"
|
|
"github.com/sqlc-dev/sqlc/internal/shfmt"
|
|
)
|
|
|
|
type CreateDatabaseRequest struct {
|
|
Engine string
|
|
Migrations []string
|
|
Prefix string
|
|
}
|
|
|
|
type CreateDatabaseResponse struct {
|
|
Uri string
|
|
}
|
|
|
|
type Client interface {
|
|
CreateDatabase(context.Context, *CreateDatabaseRequest) (*CreateDatabaseResponse, error)
|
|
Close(context.Context)
|
|
}
|
|
|
|
var flight singleflight.Group
|
|
|
|
type ManagedClient struct {
|
|
cache *poolcache.Cache
|
|
replacer *shfmt.Replacer
|
|
servers []config.Server
|
|
}
|
|
|
|
func dbid(migrations []string) string {
|
|
h := fnv.New64()
|
|
for _, query := range migrations {
|
|
io.WriteString(h, query)
|
|
}
|
|
return fmt.Sprintf("%x", h.Sum(nil))
|
|
}
|
|
|
|
func (m *ManagedClient) CreateDatabase(ctx context.Context, req *CreateDatabaseRequest) (*CreateDatabaseResponse, error) {
|
|
hash := dbid(req.Migrations)
|
|
prefix := req.Prefix
|
|
if prefix == "" {
|
|
prefix = "sqlc_managed"
|
|
}
|
|
name := fmt.Sprintf("%s_%s", prefix, hash)
|
|
|
|
engine := config.Engine(req.Engine)
|
|
switch engine {
|
|
case config.EngineMySQL:
|
|
// pass
|
|
case config.EnginePostgreSQL:
|
|
// pass
|
|
default:
|
|
return nil, fmt.Errorf("unsupported engine: %s", engine)
|
|
}
|
|
|
|
var base string
|
|
for _, server := range m.servers {
|
|
if server.Engine == engine {
|
|
base = server.URI
|
|
break
|
|
}
|
|
}
|
|
|
|
if strings.TrimSpace(base) == "" {
|
|
return nil, fmt.Errorf("no PostgreSQL database server found")
|
|
}
|
|
|
|
serverUri := m.replacer.Replace(base)
|
|
pool, err := m.cache.Open(ctx, serverUri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
uri, err := url.Parse(serverUri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
uri.Path = "/" + name
|
|
|
|
key := uri.String()
|
|
_, err, _ = flight.Do(key, func() (any, error) {
|
|
// TODO: Use a parameterized query
|
|
row := pool.QueryRow(ctx,
|
|
fmt.Sprintf(`SELECT datname FROM pg_database WHERE datname = '%s'`, name))
|
|
|
|
var datname string
|
|
if err := row.Scan(&datname); err == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
if _, err := pool.Exec(ctx, fmt.Sprintf(`CREATE DATABASE "%s"`, name)); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conn, err := pgx.Connect(ctx, uri.String())
|
|
if err != nil {
|
|
pool.Exec(ctx, fmt.Sprintf(`DROP DATABASE IF EXISTS "%s" WITH (FORCE)`, name))
|
|
return nil, fmt.Errorf("connect %s: %s", name, err)
|
|
}
|
|
defer conn.Close(ctx)
|
|
|
|
var migrationErr error
|
|
for _, q := range req.Migrations {
|
|
if len(strings.TrimSpace(q)) == 0 {
|
|
continue
|
|
}
|
|
if _, err := conn.Exec(ctx, q); err != nil {
|
|
migrationErr = fmt.Errorf("%s: %s", q, err)
|
|
break
|
|
}
|
|
}
|
|
|
|
if migrationErr != nil {
|
|
pool.Exec(ctx, fmt.Sprintf(`DROP DATABASE IF EXISTS "%s" WITH (FORCE)`, name))
|
|
return nil, migrationErr
|
|
}
|
|
|
|
return nil, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &CreateDatabaseResponse{Uri: key}, err
|
|
}
|
|
|
|
func (m *ManagedClient) Close(ctx context.Context) {
|
|
m.cache.Close()
|
|
}
|
|
|
|
func NewClient(servers []config.Server) *ManagedClient {
|
|
return &ManagedClient{
|
|
cache: poolcache.New(),
|
|
servers: servers,
|
|
replacer: shfmt.NewReplacer(nil),
|
|
}
|
|
}
|