Skip to content

Commit

Permalink
feat: Add apply subcommand (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
ginokent authored Jan 1, 2024
2 parents 087e689 + b9e8a45 commit 29ffe07
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 13 deletions.
3 changes: 1 addition & 2 deletions .envrc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
# shellcheck disable=SC2148

# Define environment variables that are not referenced in the container.
REPO_ROOT=$(git rev-parse --show-toplevel)
export REPO_ROOT
export REPO_ROOT=$(git rev-parse --show-toplevel)
export PATH="${REPO_ROOT}/.local/bin:${REPO_ROOT}/.bin:${PATH}"
export DOCKER_BUILDKIT="1"
export COMPOSE_DOCKER_CLI_BUILD="1"
Expand Down
47 changes: 41 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ options:
```console
$ ddlctl diff --help
Usage:
ddlctl diff [options] --dialect <DDL dialect> --src <source> --dst <destination>
ddlctl diff [options] --dialect <DDL dialect> <DDL source before> <DDL source after>

Description:
command "ddlctl diff" description
Expand All @@ -186,20 +186,55 @@ options:
show usage
```

### `ddlctl apply`

```console
$ ddlctl apply --help
Usage:
ddlctl apply [options] --dialect <DDL dialect> <DSN to apply> <DDL source>

Description:
command "ddlctl apply" description

options:
--lang (env: DDLCTL_LANGUAGE, default: go)
programming language to generate DDL
--dialect (env: DDLCTL_DIALECT, default: )
SQL dialect to generate DDL
--column-tag-go (env: DDLCTL_COLUMN_TAG_GO, default: db)
column annotation key for Go struct tag
--ddl-tag-go (env: DDLCTL_DDL_TAG_GO, default: ddlctl)
DDL annotation key for Go struct tag
--pk-tag-go (env: DDLCTL_PK_TAG_GO, default: pk)
primary key annotation key for Go struct tag
--auto-approve (env: DDLCTL_AUTO_APPROVE, default: false)
auto approve
--help (default: false)
show usage
```

## TODO

- dialect
- `generate` subcommand
- `generate` subcommand
- dialect
- [x] Support `mysql`
- [x] Support `postgres`
- [x] Support `cockroachdb`
- [x] Support `spanner`
- [ ] Support `sqlite3`
- `diff` subcommand
- lang
- [x] Support `go`
- `diff` subcommand
- dialect
- [ ] Support `mysql`
- [ ] Support `postgres`
- [x] Support `cockroachdb`
- [ ] Support `spanner`
- [ ] Support `sqlite3`
- `apply` subcommand
- dialect
- [ ] Support `mysql`
- [ ] Support `postgres`
- [x] Support `cockroachdb`
- [ ] Support `spanner`
- [ ] Support `sqlite3`
- lang
- [x] Support `go`
20 changes: 20 additions & 0 deletions internal/config/auto_approve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package config

import (
"context"

cliz "github.com/kunitsucom/util.go/exp/cli"

"github.com/kunitsucom/ddlctl/internal/consts"
)

func loadAutoApprove(_ context.Context, cmd *cliz.Command) bool {
v, _ := cmd.GetOptionBool(consts.OptionAutoApprove)
return v
}

func AutoApprove() bool {
globalConfigMu.RLock()
defer globalConfigMu.RUnlock()
return globalConfig.AutoApprove
}
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type config struct {
Dialect string `json:"dialect"`
Source string `json:"source"`
Destination string `json:"destination"`
AutoApprove bool `json:"auto_approve"`
// Golang
ColumnTagGo string `json:"column_tag_go"`
DDLTagGo string `json:"ddl_tag_go"`
Expand Down Expand Up @@ -80,6 +81,7 @@ func load(ctx context.Context) (cfg *config, err error) { //nolint:unparam
Dialect: loadDialect(ctx, cmd),
Source: loadSource(ctx, cmd),
Destination: loadDestination(ctx, cmd),
AutoApprove: loadAutoApprove(ctx, cmd),
ColumnTagGo: loadColumnTagGo(ctx, cmd),
DDLTagGo: loadDDLTagGo(ctx, cmd),
PKTagGo: loadPKTagGo(ctx, cmd),
Expand Down
3 changes: 3 additions & 0 deletions internal/consts/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const (
OptionDestination = "dst"
EnvKeyDestination = "DDLCTL_DESTINATION"

OptionAutoApprove = "auto-approve"
EnvKeyAutoApprove = "DDLCTL_AUTO_APPROVE"

// Golang
OptionColumnTagGo = "column-tag-go"
EnvKeyColumnTagGo = "DDLCTL_COLUMN_TAG_GO"
Expand Down
15 changes: 14 additions & 1 deletion pkg/ddlctl/ddlctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,23 @@ func DDLCtl(ctx context.Context) error {
},
{
Name: "diff",
Usage: "ddlctl diff [options] --dialect <DDL dialect> --src <source> --dst <destination>",
Usage: "ddlctl diff [options] --dialect <DDL dialect> <DDL source before> <DDL source after>",
Options: opts,
RunFunc: Diff,
},
{
Name: "apply",
Usage: "ddlctl apply [options] --dialect <DDL dialect> <DSN to apply> <DDL source>",
Options: append(opts,
&cliz.BoolOption{
Name: consts.OptionAutoApprove,
Environment: consts.EnvKeyAutoApprove,
Description: "auto approve",
Default: cliz.Default(false),
},
),
RunFunc: Apply,
},
},
Options: []cliz.Option{
&cliz.BoolOption{
Expand Down
96 changes: 96 additions & 0 deletions pkg/ddlctl/ddlctl_apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package ddlctl

import (
"bufio"
"context"
"fmt"
"os"
"strings"

sqlz "github.com/kunitsucom/util.go/database/sql"
errorz "github.com/kunitsucom/util.go/errors"

"github.com/kunitsucom/ddlctl/internal/config"
"github.com/kunitsucom/ddlctl/internal/consts"
apperr "github.com/kunitsucom/ddlctl/pkg/errors"
)

//nolint:cyclop,funlen
func Apply(ctx context.Context, args []string) error {
if _, err := config.Load(ctx); err != nil {
return errorz.Errorf("config.Load: %w", err)
}

if len(args) != 2 {
return errorz.Errorf("args=%v: %w", args, apperr.ErrTwoArgumentsRequired)
}

left, right, err := resolve(ctx, config.Dialect(), args[0], args[1])
if err != nil {
return errorz.Errorf("resolve: %w", err)
}

buf := new(strings.Builder)
if err := diff(buf, left, right); err != nil {
return errorz.Errorf("diff: %w", err)
}

msg := `
ddlctl will exec the following DDL queries:
-- 8< --
` + buf.String() + `
-- >8 --
Do you want to apply these DDL?
ddlctl will exec the DDL queries described above.
Only 'yes' will be accepted to approve.
Enter a value: `

if _, err := os.Stdout.WriteString(msg); err != nil {
return errorz.Errorf("os.Stdout.WriteString: %w", err)
}

if config.AutoApprove() {
if _, err := os.Stdout.WriteString(fmt.Sprintf("yes (via --%s option)\n", consts.OptionAutoApprove)); err != nil {
return errorz.Errorf("os.Stdout.WriteString: %w", err)
}
} else {
if err := prompt(); err != nil {
return errorz.Errorf("prompt: %w", err)
}
}

os.Stdout.WriteString("\nexecuting...\n")

db, err := sqlz.OpenContext(ctx, _postgres, args[0])
if err != nil {
return errorz.Errorf("sqlz.OpenContext: %w", err)
}
defer db.Close()

if _, err := db.ExecContext(ctx, buf.String()); err != nil {
return errorz.Errorf("db.ExecContext: %w", err)
}

os.Stdout.WriteString("done\n")

return nil
}

func prompt() error {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
userInput := scanner.Text()

switch userInput {
case "yes", "YES":
return nil
default:
return errorz.Errorf("userInput=%s: %w", userInput, apperr.ErrUserCanceled)
}
}
19 changes: 16 additions & 3 deletions pkg/ddlctl/ddlctl_diff.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package ddlctl

import (
"bytes"
"context"
"io"
"os"
"regexp"
"strings"
Expand All @@ -28,15 +30,24 @@ func Diff(ctx context.Context, args []string) error {
return errorz.Errorf("config.Load: %w", err)
}

if len(args) != 2 {
return errorz.Errorf("args=%v: %w", args, apperr.ErrTwoArgumentsRequired)
}

left, right, err := resolve(ctx, config.Dialect(), args[0], args[1])
if err != nil {
return errorz.Errorf("resolve: %w", err)
}

if err := diff(left, right); err != nil {
buf := bytes.NewBuffer(nil)
if err := diff(buf, left, right); err != nil {
return errorz.Errorf("diff: %w", err)
}

if _, err := io.Copy(os.Stdout, buf); err != nil {
return errorz.Errorf("io.Copy: %w", err)
}

return nil
}

Expand Down Expand Up @@ -134,7 +145,7 @@ func generateDDLForDiff(ctx context.Context, src string) (string, error) {
}

//nolint:cyclop
func diff(src, dst string) error {
func diff(out io.Writer, src, dst string) error {
logs.Debug.Printf("src: %q", src)
logs.Debug.Printf("dst: %q", dst)

Expand Down Expand Up @@ -172,7 +183,9 @@ func diff(src, dst string) error {
return errorz.Errorf("pgddl.Diff: %w", err)
}

os.Stdout.WriteString(result.String())
if _, err := io.WriteString(out, result.String()); err != nil {
return errorz.Errorf("io.WriteString: %w", err)
}

return nil
case "":
Expand Down
3 changes: 2 additions & 1 deletion pkg/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import "errors"

var (
ErrNotSupported = errors.New("not supported")
ErrUserCanceled = errors.New("user canceled")
ErrDialectIsEmpty = errors.New("dialect is empty")
ErrDDLTagGoAnnotationNotFoundInSource = errors.New("ddl-tag-go annotation not found in source")
ErrDiffRequiresTwoArguments = errors.New("diff requires two arguments")
ErrTwoArgumentsRequired = errors.New("two arguments required")
ErrBothArgumentsIsDSN = errors.New("both arguments is dsn")
ErrBothArgumentsAreNotDSNOrSQLFile = errors.New("both arguments are not dsn or sql file")
)

0 comments on commit 29ffe07

Please sign in to comment.