Skip to content

Commit f542f95

Browse files
authored
Merge pull request #649 from k1LoW/driver-plugin
Support external database driver
2 parents 576d46c + f5145fe commit f542f95

File tree

4 files changed

+84
-20
lines changed

4 files changed

+84
-20
lines changed

Makefile

+4-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ TBLS ?= ./tbls
1616

1717
default: test
1818

19-
ci: depsdev build db test testdoc testdoc_hide_auto_increment test_too_many_tables test_json test_ext_subcommand test_jsonschema doc
19+
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
2020

2121
ci_windows: depsdev build db_sqlite testdoc_sqlite
2222

@@ -147,6 +147,9 @@ test_ext_subcommand: build
147147
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
148148
echo hello | env PATH="${PWD}/testdata/bin:${PATH}" $(TBLS) echo -c ./testdata/ext_subcommand_tbls.yml | grep 'STDIN=hello' > /dev/null
149149

150+
test_ext_driver: build
151+
env PATH="${PWD}/testdata/bin:${PATH}" $(TBLS) ls --dsn foodb://bar | grep 'users' > /dev/null
152+
150153
test_jsonschema:
151154
cd scripts/jsonschema && go run main.go | diff -u ../../spec/tbls.schema.json_schema.json -
152155

cmd/root.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"strconv"
3030
"strings"
3131

32+
"github.com/cli/safeexec"
3233
"github.com/k1LoW/errors"
3334
"github.com/k1LoW/tbls/cmdutil"
3435
"github.com/k1LoW/tbls/config"
@@ -127,7 +128,7 @@ var rootCmd = &cobra.Command{
127128

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

171-
c := exec.Command(path, args...) // #nosec
172+
c := exec.Command(bin, args...) // #nosec
172173
c.Env = envs
173174
c.Stdout = os.Stdout
174175
c.Stdin = os.Stdin
@@ -253,6 +254,10 @@ func getExtSubCmds(prefix string) ([]string, error) {
253254
if !strings.HasPrefix(e.Name(), fmt.Sprintf("%s-", prefix)) {
254255
continue
255256
}
257+
// Exclude external driver
258+
if strings.HasPrefix(e.Name(), fmt.Sprintf("%s-driver-", prefix)) {
259+
continue
260+
}
256261
fi, err := e.Info()
257262
if err != nil {
258263
continue

datasource/datasource.go

+66-17
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import (
66
"fmt"
77
"io"
88
"net/http"
9+
"net/url"
910
"os"
11+
"os/exec"
1012
"path/filepath"
13+
"slices"
1114
"strings"
1215
"time"
1316

17+
"github.com/cli/safeexec"
1418
"github.com/k1LoW/errors"
1519
"github.com/k1LoW/ghfs"
1620
"github.com/k1LoW/go-github-client/v58/factory"
@@ -28,6 +32,15 @@ import (
2832
"github.com/xo/dburl"
2933
)
3034

35+
var supportDriversWithDburl = []string{
36+
"postgres",
37+
"mysql",
38+
"sqlite3",
39+
"sqlserver",
40+
"snowflake",
41+
"clickhouse",
42+
}
43+
3144
// Analyze database
3245
func Analyze(dsn config.DSN) (_ *schema.Schema, err error) {
3346
defer func() {
@@ -57,8 +70,12 @@ func Analyze(dsn config.DSN) (_ *schema.Schema, err error) {
5770
}
5871
s := &schema.Schema{}
5972
u, err := dburl.Parse(urlstr)
73+
if err != nil || !slices.Contains(supportDriversWithDburl, u.Driver) {
74+
// Try ext driver
75+
return AnalyzeWithExtDriver(urlstr)
76+
}
6077
if err != nil {
61-
return s, err
78+
return nil, err
6279
}
6380
splitted := strings.Split(u.Short(), "/")
6481
if len(splitted) < 2 {
@@ -91,13 +108,13 @@ func Analyze(dsn config.DSN) (_ *schema.Schema, err error) {
91108

92109
db, err := dburl.Open(urlstr)
93110
if err != nil {
94-
return s, errors.WithStack(err)
111+
return nil, errors.WithStack(err)
95112
}
96113
defer func() {
97114
_ = db.Close()
98115
}()
99116
if err := db.Ping(); err != nil {
100-
return s, errors.WithStack(err)
117+
return nil, errors.WithStack(err)
101118
}
102119

103120
var driver drivers.Driver
@@ -118,7 +135,7 @@ func Analyze(dsn config.DSN) (_ *schema.Schema, err error) {
118135
driver, err = mysql.New(db, opts...)
119136
}
120137
if err != nil {
121-
return s, err
138+
return nil, err
122139
}
123140
case "sqlite3":
124141
s.Name = splitted[len(splitted)-1]
@@ -137,7 +154,7 @@ func Analyze(dsn config.DSN) (_ *schema.Schema, err error) {
137154
}
138155
err = driver.Analyze(s)
139156
if err != nil {
140-
return s, err
157+
return nil, err
141158
}
142159
return s, nil
143160
}
@@ -150,23 +167,23 @@ func AnalyzeHTTPResource(dsn config.DSN) (_ *schema.Schema, err error) {
150167
s := &schema.Schema{}
151168
req, err := http.NewRequest("GET", dsn.URL, nil)
152169
if err != nil {
153-
return s, err
170+
return nil, err
154171
}
155172
for k, v := range dsn.Headers {
156173
req.Header.Add(k, v)
157174
}
158175
client := &http.Client{Timeout: time.Duration(10) * time.Second}
159176
resp, err := client.Do(req)
160177
if err != nil {
161-
return s, err
178+
return nil, err
162179
}
163180
defer resp.Body.Close()
164181
dec := json.NewDecoder(resp.Body)
165182
if err := dec.Decode(s); err != nil {
166-
return s, err
183+
return nil, err
167184
}
168185
if err := s.Repair(); err != nil {
169-
return s, err
186+
return nil, err
170187
}
171188
return s, nil
172189
}
@@ -197,10 +214,10 @@ func AnalyzeGitHubContent(dsn config.DSN) (_ *schema.Schema, err error) {
197214
}
198215
dec := json.NewDecoder(bytes.NewReader(b))
199216
if err := dec.Decode(s); err != nil {
200-
return s, err
217+
return nil, err
201218
}
202219
if err := s.Repair(); err != nil {
203-
return s, err
220+
return nil, err
204221
}
205222
return s, nil
206223
}
@@ -214,14 +231,14 @@ func AnalyzeJSON(urlstr string) (_ *schema.Schema, err error) {
214231
splitted := strings.Split(urlstr, "json://")
215232
file, err := os.Open(splitted[1])
216233
if err != nil {
217-
return s, err
234+
return nil, err
218235
}
219236
dec := json.NewDecoder(file)
220237
if err := dec.Decode(s); err != nil {
221-
return s, err
238+
return nil, err
222239
}
223240
if err := s.Repair(); err != nil {
224-
return s, err
241+
return nil, err
225242
}
226243
return s, nil
227244
}
@@ -243,15 +260,47 @@ func AnalyzeJSONStringOrFile(strOrPath string) (s *schema.Schema, err error) {
243260
} else {
244261
buf, err = os.Open(filepath.Clean(strOrPath))
245262
if err != nil {
246-
return s, err
263+
return nil, err
247264
}
248265
}
249266
dec := json.NewDecoder(buf)
250267
if err := dec.Decode(s); err != nil {
251-
return s, err
268+
return nil, err
269+
}
270+
if err := s.Repair(); err != nil {
271+
return nil, err
272+
}
273+
return s, nil
274+
}
275+
276+
// AnalyzeWithExtDriver analyze with external driver command.
277+
func AnalyzeWithExtDriver(urlstr string) (*schema.Schema, error) {
278+
u, err := url.Parse(urlstr)
279+
if err != nil {
280+
return nil, err
281+
}
282+
scheme := u.Scheme
283+
bin, err := safeexec.LookPath(fmt.Sprintf("tbls-driver-%s", scheme))
284+
if err != nil {
285+
return nil, fmt.Errorf("unsupported driver '%s'", scheme)
286+
}
287+
envs := os.Environ()
288+
envs = append(envs, fmt.Sprintf("TBLS_DSN=%s", urlstr))
289+
c := exec.Command(bin)
290+
buf := new(bytes.Buffer)
291+
c.Stdout = buf
292+
c.Stderr = os.Stderr
293+
c.Env = envs
294+
if err := c.Run(); err != nil {
295+
return nil, err
296+
}
297+
s := &schema.Schema{}
298+
dec := json.NewDecoder(buf)
299+
if err := dec.Decode(s); err != nil {
300+
return nil, err
252301
}
253302
if err := s.Repair(); err != nil {
254-
return s, err
303+
return nil, err
255304
}
256305
return s, nil
257306
}

testdata/bin/tbls-driver-foodb

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
dir=$(cd $(dirname $0); pwd)
6+
7+
cat ${dir}/../test_schema.json

0 commit comments

Comments
 (0)