Skip to content

Commit

Permalink
feat: Support external database driver
Browse files Browse the repository at this point in the history
  • Loading branch information
k1LoW committed Jan 26, 2025
1 parent 576d46c commit f5145fe
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 20 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ TBLS ?= ./tbls

default: test

ci: depsdev build db test testdoc testdoc_hide_auto_increment test_too_many_tables test_json test_ext_subcommand test_jsonschema doc
ci: depsdev build db test testdoc testdoc_hide_auto_increment test_too_many_tables test_json test_ext_subcommand test_ext_driver test_jsonschema doc

ci_windows: depsdev build db_sqlite testdoc_sqlite

Expand Down Expand Up @@ -147,6 +147,9 @@ test_ext_subcommand: build
env PATH="${PWD}/testdata/bin:${PATH}" TBLS_DSN=pg://postgres:pgpass@localhost:55432/testdb?sslmode=disable $(TBLS) echo | grep 'TBLS_DSN=pg://postgres:pgpass@localhost:55432/testdb?sslmode=disable' > /dev/null
echo hello | env PATH="${PWD}/testdata/bin:${PATH}" $(TBLS) echo -c ./testdata/ext_subcommand_tbls.yml | grep 'STDIN=hello' > /dev/null

test_ext_driver: build
env PATH="${PWD}/testdata/bin:${PATH}" $(TBLS) ls --dsn foodb://bar | grep 'users' > /dev/null

test_jsonschema:
cd scripts/jsonschema && go run main.go | diff -u ../../spec/tbls.schema.json_schema.json -

Expand Down
9 changes: 7 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"strconv"
"strings"

"github.com/cli/safeexec"
"github.com/k1LoW/errors"
"github.com/k1LoW/tbls/cmdutil"
"github.com/k1LoW/tbls/config"
Expand Down Expand Up @@ -127,7 +128,7 @@ var rootCmd = &cobra.Command{

envs := os.Environ()
subCmd := args[0]
path, err := exec.LookPath(version.Name + "-" + subCmd)
bin, err := safeexec.LookPath(version.Name + "-" + subCmd)
if err != nil {
if strings.HasPrefix(subCmd, "-") {
cmd.PrintErrf("Error: unknown flag: '%s'\n", subCmd)
Expand Down Expand Up @@ -168,7 +169,7 @@ var rootCmd = &cobra.Command{
envs = append(envs, fmt.Sprintf("TBLS_SCHEMA=%s", tmpfile.Name()))
}

c := exec.Command(path, args...) // #nosec
c := exec.Command(bin, args...) // #nosec
c.Env = envs
c.Stdout = os.Stdout
c.Stdin = os.Stdin
Expand Down Expand Up @@ -253,6 +254,10 @@ func getExtSubCmds(prefix string) ([]string, error) {
if !strings.HasPrefix(e.Name(), fmt.Sprintf("%s-", prefix)) {
continue
}
// Exclude external driver
if strings.HasPrefix(e.Name(), fmt.Sprintf("%s-driver-", prefix)) {
continue
}
fi, err := e.Info()
if err != nil {
continue
Expand Down
83 changes: 66 additions & 17 deletions datasource/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"time"

"github.com/cli/safeexec"
"github.com/k1LoW/errors"
"github.com/k1LoW/ghfs"
"github.com/k1LoW/go-github-client/v58/factory"
Expand All @@ -28,6 +32,15 @@ import (
"github.com/xo/dburl"
)

var supportDriversWithDburl = []string{
"postgres",
"mysql",
"sqlite3",
"sqlserver",
"snowflake",
"clickhouse",
}

// Analyze database
func Analyze(dsn config.DSN) (_ *schema.Schema, err error) {
defer func() {
Expand Down Expand Up @@ -57,8 +70,12 @@ func Analyze(dsn config.DSN) (_ *schema.Schema, err error) {
}
s := &schema.Schema{}
u, err := dburl.Parse(urlstr)
if err != nil || !slices.Contains(supportDriversWithDburl, u.Driver) {
// Try ext driver
return AnalyzeWithExtDriver(urlstr)
}
if err != nil {
return s, err
return nil, err
}
splitted := strings.Split(u.Short(), "/")
if len(splitted) < 2 {
Expand Down Expand Up @@ -91,13 +108,13 @@ func Analyze(dsn config.DSN) (_ *schema.Schema, err error) {

db, err := dburl.Open(urlstr)
if err != nil {
return s, errors.WithStack(err)
return nil, errors.WithStack(err)
}
defer func() {
_ = db.Close()
}()
if err := db.Ping(); err != nil {
return s, errors.WithStack(err)
return nil, errors.WithStack(err)
}

var driver drivers.Driver
Expand All @@ -118,7 +135,7 @@ func Analyze(dsn config.DSN) (_ *schema.Schema, err error) {
driver, err = mysql.New(db, opts...)
}
if err != nil {
return s, err
return nil, err
}
case "sqlite3":
s.Name = splitted[len(splitted)-1]
Expand All @@ -137,7 +154,7 @@ func Analyze(dsn config.DSN) (_ *schema.Schema, err error) {
}
err = driver.Analyze(s)
if err != nil {
return s, err
return nil, err
}
return s, nil
}
Expand All @@ -150,23 +167,23 @@ func AnalyzeHTTPResource(dsn config.DSN) (_ *schema.Schema, err error) {
s := &schema.Schema{}
req, err := http.NewRequest("GET", dsn.URL, nil)
if err != nil {
return s, err
return nil, err
}
for k, v := range dsn.Headers {
req.Header.Add(k, v)
}
client := &http.Client{Timeout: time.Duration(10) * time.Second}
resp, err := client.Do(req)
if err != nil {
return s, err
return nil, err
}
defer resp.Body.Close()
dec := json.NewDecoder(resp.Body)
if err := dec.Decode(s); err != nil {
return s, err
return nil, err
}
if err := s.Repair(); err != nil {
return s, err
return nil, err
}
return s, nil
}
Expand Down Expand Up @@ -197,10 +214,10 @@ func AnalyzeGitHubContent(dsn config.DSN) (_ *schema.Schema, err error) {
}
dec := json.NewDecoder(bytes.NewReader(b))
if err := dec.Decode(s); err != nil {
return s, err
return nil, err
}
if err := s.Repair(); err != nil {
return s, err
return nil, err
}
return s, nil
}
Expand All @@ -214,14 +231,14 @@ func AnalyzeJSON(urlstr string) (_ *schema.Schema, err error) {
splitted := strings.Split(urlstr, "json://")
file, err := os.Open(splitted[1])
if err != nil {
return s, err
return nil, err
}
dec := json.NewDecoder(file)
if err := dec.Decode(s); err != nil {
return s, err
return nil, err
}
if err := s.Repair(); err != nil {
return s, err
return nil, err
}
return s, nil
}
Expand All @@ -243,15 +260,47 @@ func AnalyzeJSONStringOrFile(strOrPath string) (s *schema.Schema, err error) {
} else {
buf, err = os.Open(filepath.Clean(strOrPath))
if err != nil {
return s, err
return nil, err
}
}
dec := json.NewDecoder(buf)
if err := dec.Decode(s); err != nil {
return s, err
return nil, err
}
if err := s.Repair(); err != nil {
return nil, err
}
return s, nil
}

// AnalyzeWithExtDriver analyze with external driver command.
func AnalyzeWithExtDriver(urlstr string) (*schema.Schema, error) {
u, err := url.Parse(urlstr)
if err != nil {
return nil, err
}
scheme := u.Scheme
bin, err := safeexec.LookPath(fmt.Sprintf("tbls-driver-%s", scheme))
if err != nil {
return nil, fmt.Errorf("unsupported driver '%s'", scheme)
}
envs := os.Environ()
envs = append(envs, fmt.Sprintf("TBLS_DSN=%s", urlstr))
c := exec.Command(bin)
buf := new(bytes.Buffer)
c.Stdout = buf
c.Stderr = os.Stderr
c.Env = envs
if err := c.Run(); err != nil {
return nil, err
}
s := &schema.Schema{}
dec := json.NewDecoder(buf)
if err := dec.Decode(s); err != nil {
return nil, err
}
if err := s.Repair(); err != nil {
return s, err
return nil, err
}
return s, nil
}
7 changes: 7 additions & 0 deletions testdata/bin/tbls-driver-foodb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

set -e

dir=$(cd $(dirname $0); pwd)

cat ${dir}/../test_schema.json

0 comments on commit f5145fe

Please sign in to comment.