Skip to content

Commit

Permalink
feat(verify): Update verify to work with managed databases (#3425)
Browse files Browse the repository at this point in the history
The output still needs work, but this replaces the cloud-based verify
  • Loading branch information
kyleconroy authored Jun 6, 2024
1 parent 984437e commit e3f888f
Show file tree
Hide file tree
Showing 3 changed files with 332 additions and 96 deletions.
124 changes: 92 additions & 32 deletions internal/cmd/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ package cmd

import (
"context"
"database/sql"
"fmt"
"log/slog"
"os"

_ "github.com/jackc/pgx/v5/stdlib"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"

"github.com/sqlc-dev/sqlc/internal/bundler"
"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/dbmanager"
"github.com/sqlc-dev/sqlc/internal/migrations"
"github.com/sqlc-dev/sqlc/internal/plugin"
"github.com/sqlc-dev/sqlc/internal/quickdb"
quickdbv1 "github.com/sqlc-dev/sqlc/internal/quickdb/v1"
pb "github.com/sqlc-dev/sqlc/internal/quickdb/v1"
"github.com/sqlc-dev/sqlc/internal/sql/sqlpath"
)

func init() {
Expand All @@ -32,7 +40,6 @@ var verifyCmd = &cobra.Command{
Against: against,
}
if err := Verify(cmd.Context(), dir, name, opts); err != nil {
fmt.Fprintf(stderr, "error verifying: %s\n", err)
os.Exit(1)
}
return nil
Expand All @@ -41,50 +48,103 @@ var verifyCmd = &cobra.Command{

func Verify(ctx context.Context, dir, filename string, opts *Options) error {
stderr := opts.Stderr
configPath, conf, err := readConfig(stderr, dir, filename)
_, conf, err := readConfig(stderr, dir, filename)
if err != nil {
return err
}

client, err := quickdb.NewClientFromConfig(conf.Cloud)
if err != nil {
return fmt.Errorf("client init failed: %w", err)
}
p := &pusher{}
if err := Process(ctx, p, dir, filename, opts); err != nil {
return err
}
req, err := bundler.BuildRequest(ctx, dir, configPath, p.results, nil)
if err != nil {
return err
}
if val := os.Getenv("CI"); val != "" {
req.Annotations["env.ci"] = val
}
if val := os.Getenv("GITHUB_RUN_ID"); val != "" {
req.Annotations["github.run.id"] = val
}

resp, err := client.VerifyQuerySets(ctx, &quickdbv1.VerifyQuerySetsRequest{
Against: opts.Against,
SqlcVersion: req.SqlcVersion,
QuerySets: req.QuerySets,
Config: req.Config,
Annotations: req.Annotations,
manager := dbmanager.NewClient(conf.Servers)

// Get query sets from a previous archive by tag. If no tag is provided, get
// the latest query sets.
previous, err := client.GetQuerySets(ctx, &pb.GetQuerySetsRequest{
Tag: opts.Against,
})
if err != nil {
return err
}
summaryPath := os.Getenv("GITHUB_STEP_SUMMARY")
if resp.Summary != "" {
if _, err := os.Stat(summaryPath); err == nil {
if err := os.WriteFile(summaryPath, []byte(resp.Summary), 0644); err != nil {

// Create a mapping of name to query set
existing := map[string]config.SQL{}
for _, qs := range conf.SQL {
existing[qs.Name] = qs
}

for _, qs := range previous.QuerySets {
// TODO: Create a function for this so that we can return early on errors

check := func() error {
if qs.Name == "" {
return fmt.Errorf("unnamed query set")
}

current, found := existing[qs.Name]
if !found {
return fmt.Errorf("unknown query set: %s", qs.Name)
}

// Read the schema files into memory, removing rollback statements
var ddl []string
files, err := sqlpath.Glob(current.Schema)
if err != nil {
return err
}
for _, schema := range files {
contents, err := os.ReadFile(schema)
if err != nil {
return fmt.Errorf("read file: %w", err)
}
ddl = append(ddl, migrations.RemoveRollbackStatements(string(contents)))
}

var codegen plugin.GenerateRequest
if err := proto.Unmarshal(qs.CodegenRequest.Contents, &codegen); err != nil {
return err
}

// Create (or re-use) a database to verify against
resp, err := manager.CreateDatabase(ctx, &dbmanager.CreateDatabaseRequest{
Migrations: ddl,
})
if err != nil {
return err
}

db, err := sql.Open("pgx", resp.Uri)
if err != nil {
return err
}
defer db.Close()

for _, query := range codegen.Queries {
stmt, err := db.PrepareContext(ctx, query.Text)
if err != nil {
fmt.Fprintf(stderr, "Failed to prepare the following query:\n")
fmt.Fprintf(stderr, "%s\n", query.Text)
fmt.Fprintf(stderr, "Error was: %s\n", err)
continue
}
if err := stmt.Close(); err != nil {
slog.Error("stmt.Close failed", "err", err)
}
}

return nil
}

if err := check(); err != nil {
fmt.Fprintf(stderr, "FAIL\t%s\n", qs.Name)
} else {
fmt.Fprintf(stderr, "ok\t%s\n", qs.Name)
}
}
fmt.Fprintf(stderr, resp.Output)
if resp.Errored {
return fmt.Errorf("BREAKING CHANGES DETECTED")
}

// return fmt.Errorf("BREAKING CHANGES DETECTED")

return nil
}
Loading

0 comments on commit e3f888f

Please # to comment.