Skip to content

Commit 01947a6

Browse files
authored
feat(createdb): Create ephemeral databases (#2894)
* feat(createdb): Create ephemeral databases and return a database URI
1 parent e84018d commit 01947a6

File tree

5 files changed

+109
-1
lines changed

5 files changed

+109
-1
lines changed

internal/cmd/cmd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
)
2626

2727
func init() {
28+
createDBCmd.Flags().StringP("queryset", "", "", "name of the queryset to use")
2829
uploadCmd.Flags().BoolP("dry-run", "", false, "dump upload request (default: false)")
2930
initCmd.Flags().BoolP("v1", "", false, "generate v1 config yaml file")
3031
initCmd.Flags().BoolP("v2", "", true, "generate v2 config yaml file")
@@ -41,6 +42,7 @@ func Do(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) int
4142
rootCmd.PersistentFlags().Bool("no-database", false, "disable database connections (default: false)")
4243

4344
rootCmd.AddCommand(checkCmd)
45+
rootCmd.AddCommand(createDBCmd)
4446
rootCmd.AddCommand(diffCmd)
4547
rootCmd.AddCommand(genCmd)
4648
rootCmd.AddCommand(initCmd)

internal/cmd/createdb.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"runtime/trace"
8+
9+
"github.com/spf13/cobra"
10+
"github.com/sqlc-dev/sqlc/internal/config"
11+
"github.com/sqlc-dev/sqlc/internal/migrations"
12+
"github.com/sqlc-dev/sqlc/internal/quickdb"
13+
pb "github.com/sqlc-dev/sqlc/internal/quickdb/v1"
14+
"github.com/sqlc-dev/sqlc/internal/sql/sqlpath"
15+
)
16+
17+
var createDBCmd = &cobra.Command{
18+
Use: "createdb",
19+
Short: "Create an ephemeral database",
20+
Args: cobra.NoArgs,
21+
RunE: func(cmd *cobra.Command, args []string) error {
22+
defer trace.StartRegion(cmd.Context(), "createdb").End()
23+
stderr := cmd.ErrOrStderr()
24+
dir, filename := getConfigPath(stderr, cmd.Flag("file"))
25+
querySetName, err := cmd.Flags().GetString("queryset")
26+
if err != nil {
27+
return err
28+
}
29+
err = CreateDB(cmd.Context(), dir, filename, querySetName, &Options{
30+
Env: ParseEnv(cmd),
31+
Stderr: stderr,
32+
})
33+
if err != nil {
34+
fmt.Fprintln(stderr, err.Error())
35+
os.Exit(1)
36+
}
37+
return nil
38+
},
39+
}
40+
41+
func CreateDB(ctx context.Context, dir, filename, querySetName string, o *Options) error {
42+
_, conf, err := o.ReadConfig(dir, filename)
43+
if err != nil {
44+
return err
45+
}
46+
// Find the first queryset with a managed database
47+
var queryset *config.SQL
48+
var count int
49+
for _, sql := range conf.SQL {
50+
sql := sql
51+
if querySetName != "" && sql.Name != querySetName {
52+
continue
53+
}
54+
if sql.Database != nil && sql.Database.Managed {
55+
queryset = &sql
56+
count += 1
57+
}
58+
}
59+
if queryset == nil && querySetName != "" {
60+
return fmt.Errorf("no queryset found with name %q", querySetName)
61+
}
62+
if queryset == nil {
63+
return fmt.Errorf("no querysets configured to use a managed database")
64+
}
65+
if count > 1 {
66+
return fmt.Errorf("multiple querysets configured to use managed databases")
67+
}
68+
if queryset.Engine != config.EnginePostgreSQL {
69+
return fmt.Errorf("managed databases currently only support PostgreSQL")
70+
}
71+
72+
var ddl []string
73+
files, err := sqlpath.Glob(queryset.Schema)
74+
if err != nil {
75+
return err
76+
}
77+
for _, schema := range files {
78+
contents, err := os.ReadFile(schema)
79+
if err != nil {
80+
return fmt.Errorf("read file: %w", err)
81+
}
82+
ddl = append(ddl, migrations.RemoveRollbackStatements(string(contents)))
83+
}
84+
85+
client, err := quickdb.NewClientFromConfig(conf.Cloud)
86+
if err != nil {
87+
return fmt.Errorf("client error: %w", err)
88+
}
89+
90+
resp, err := client.CreateEphemeralDatabase(ctx, &pb.CreateEphemeralDatabaseRequest{
91+
Engine: "postgresql",
92+
Region: quickdb.GetClosestRegion(),
93+
Migrations: ddl,
94+
})
95+
if err != nil {
96+
return fmt.Errorf("managed: create database: %w", err)
97+
}
98+
fmt.Fprintln(os.Stderr, "WARNING: This database will be removed in two minutes")
99+
fmt.Println(resp.Uri)
100+
return nil
101+
}

internal/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ type GenGo struct {
103103
}
104104

105105
type SQL struct {
106+
Name string `json:"name" yaml:"name"`
106107
Engine Engine `json:"engine,omitempty" yaml:"engine"`
107108
Schema Paths `json:"schema" yaml:"schema"`
108109
Queries Paths `json:"queries" yaml:"queries"`

internal/config/v_one.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ func (c *V1GenerateSettings) Translate() Config {
143143
pkg.StrictOrderBy = &defaultValue
144144
}
145145
conf.SQL = append(conf.SQL, SQL{
146+
Name: pkg.Name,
146147
Engine: pkg.Engine,
147148
Database: pkg.Database,
148149
Schema: pkg.Schema,

internal/config/v_two.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
"engine"
3232
],
3333
"properties": {
34+
"name": {
35+
"type": "string"
36+
},
3437
"engine": {
3538
"enum": [
3639
"postgresql",
@@ -439,4 +442,4 @@
439442
}
440443
}
441444
}
442-
}
445+
}

0 commit comments

Comments
 (0)