Skip to content

Commit 00c3da5

Browse files
authored
Add dsq binary for running SQL queries against files (#127)
* Start on dsq * Continue sketching out dsq * More sketching * Refactoring to support dsq * Working with hardcoded shape * Clean up * Fix for shape guesser * Fix for format * Go doesnt support graph or table yet * Fix build scripts for new binary * Fixes for build * More fixes
1 parent c421502 commit 00c3da5

28 files changed

+493
-260
lines changed

desktop/panel/eval.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ function canUseGoRunner(panel: PanelInfo, connectors: ConnectorInfo[]) {
9595
return false;
9696
}
9797

98-
return true;
98+
return !['table', 'graph'].includes(panel.type);
9999
}
100100

101101
export async function evalInSubprocess(

desktop/scripts/desktop.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
setenv UI_CONFIG_OVERRIDES "window.DS_CONFIG_MODE = 'desktop';"
22
yarn build-ui
33

4-
cd runner && go build -o ../build/go_desktop_runner{required_ext}
4+
cd runner && go build -o ../build/go_desktop_runner{required_ext} cmd/main.go
55

66
yarn esbuild desktop/preload.ts --external:electron --sourcemap --bundle --outfile=build/preload.js
77
yarn esbuild desktop/runner.ts --bundle --platform=node --sourcemap --external:react-native-fs --external:react-native-fetch-blob "--external:@elastic/elasticsearch" "--external:wasm-brotli" --external:prometheus-query --external:snowflake-sdk --external:ssh2 --external:ssh2-promise --external:ssh2-sftp-client --external:cpu-features --external:electron --target=node10.4 --outfile=build/desktop_runner.js

runner/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
runner
2+
main

runner/cmd/dsq/main.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"log"
9+
"os"
10+
"strings"
11+
12+
"github.com/multiprocessio/datastation/runner"
13+
)
14+
15+
func isinpipe() bool {
16+
fi, _ := os.Stdin.Stat()
17+
return (fi.Mode() & os.ModeCharDevice) == 0
18+
}
19+
20+
func resolveContentType(fileExtensionOrContentType string) string {
21+
if strings.Contains(fileExtensionOrContentType, "/") {
22+
return fileExtensionOrContentType
23+
}
24+
25+
return runner.GetMimeType("x."+fileExtensionOrContentType, runner.ContentTypeInfo{})
26+
}
27+
28+
var firstNonFlagArg = ""
29+
30+
func getResult(res interface{}) error {
31+
out := bytes.NewBuffer(nil)
32+
arg := firstNonFlagArg
33+
34+
var internalErr error
35+
if isinpipe() {
36+
mimetype := resolveContentType(arg)
37+
if mimetype == "" {
38+
return fmt.Errorf(`First argument when used in a pipe should be file extension or content type. e.g. 'cat test.csv | dsq csv "SELECT * FROM {}"'`)
39+
}
40+
41+
cti := runner.ContentTypeInfo{Type: mimetype}
42+
internalErr = runner.TransformReader(os.Stdin, "", cti, out)
43+
} else {
44+
internalErr = runner.TransformFile(arg, runner.ContentTypeInfo{}, out)
45+
}
46+
if internalErr != nil {
47+
return internalErr
48+
}
49+
50+
decoder := json.NewDecoder(out)
51+
return decoder.Decode(res)
52+
}
53+
54+
func main() {
55+
if len(os.Args) < 3 {
56+
log.Fatal(`Expected data source and query. e.g. 'dsq names.csv "SELECT name FROM {}"'`)
57+
}
58+
59+
runner.Verbose = false
60+
inputTable := "{}"
61+
lastNonFlagArg := ""
62+
for i, arg := range os.Args[1:] {
63+
if arg == "-i" || arg == "--input-table-alias" {
64+
if i > len(os.Args)-2 {
65+
log.Fatal(`Expected input table alias after flag. e.g. 'dsq -i XX names.csv "SELECT * FROM XX"'`)
66+
}
67+
68+
inputTable = os.Args[i+1]
69+
continue
70+
}
71+
72+
if arg == "-v" || arg == "--verbose" {
73+
runner.Verbose = true
74+
continue
75+
}
76+
77+
if firstNonFlagArg == "" {
78+
firstNonFlagArg = arg
79+
}
80+
81+
lastNonFlagArg = arg
82+
}
83+
84+
var res []map[string]interface{}
85+
err := getResult(&res)
86+
if err != nil {
87+
log.Fatal(err)
88+
}
89+
90+
sampleSize := 50
91+
shape, err := runner.GetArrayShape(firstNonFlagArg, res, sampleSize)
92+
if err != nil {
93+
log.Fatal(err)
94+
}
95+
96+
p0 := runner.PanelInfo{
97+
ResultMeta: runner.PanelResult{
98+
Shape: *shape,
99+
},
100+
}
101+
project := &runner.ProjectState{
102+
Pages: []runner.ProjectPage{
103+
{
104+
Panels: []runner.PanelInfo{p0},
105+
},
106+
},
107+
}
108+
connector, tmp, err := runner.MakeTmpSQLiteConnector()
109+
if err != nil {
110+
log.Fatal(err)
111+
}
112+
defer os.Remove(tmp.Name())
113+
project.Connectors = append(project.Connectors, *connector)
114+
115+
query := lastNonFlagArg
116+
query = strings.ReplaceAll(query, inputTable, "DM_getPanel(0)")
117+
panel := &runner.PanelInfo{
118+
Type: runner.DatabasePanel,
119+
Content: query,
120+
DatabasePanelInfo: &runner.DatabasePanelInfo{
121+
Database: runner.DatabasePanelInfoDatabase{
122+
ConnectorId: connector.Id,
123+
},
124+
},
125+
}
126+
127+
panelResultLoader := func(_, _ string, out interface{}) error {
128+
r := out.(*[]map[string]interface{})
129+
*r = res
130+
return nil
131+
}
132+
err = runner.EvalDatabasePanel(project, 0, panel, panelResultLoader)
133+
if err != nil {
134+
log.Fatal(err)
135+
}
136+
137+
// Dump the result to stdout
138+
fd, err := os.Open(runner.GetPanelResultsFile(project.Id, panel.Id))
139+
if err != nil {
140+
log.Fatalf("Could not open results file: %s", err)
141+
}
142+
143+
io.Copy(os.Stdout, fd)
144+
}

runner/cmd/main.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package main
2+
3+
import (
4+
"os"
5+
6+
"github.com/multiprocessio/datastation/runner"
7+
)
8+
9+
const VERSION = "development"
10+
const APP_NAME = "DataStation Runner (Go)"
11+
12+
func main() {
13+
runner.Verbose = true
14+
runner.Logln(APP_NAME + " " + VERSION)
15+
projectId := ""
16+
panelId := ""
17+
panelMetaOut := ""
18+
19+
args := os.Args
20+
for i := 0; i < len(args)-1; i++ {
21+
if args[i] == "--dsproj" {
22+
projectId = args[i+1]
23+
i++
24+
continue
25+
}
26+
27+
if args[i] == "--evalPanel" {
28+
panelId = args[i+1]
29+
i++
30+
continue
31+
}
32+
33+
if args[i] == "--metaFile" {
34+
panelMetaOut = args[i+1]
35+
i++
36+
continue
37+
}
38+
}
39+
40+
if projectId == "" {
41+
runner.Fatalln("No project id given.")
42+
}
43+
44+
if panelId == "" {
45+
runner.Fatalln("No panel id given.")
46+
}
47+
48+
if panelMetaOut == "" {
49+
runner.Fatalln("No panel meta out given.")
50+
}
51+
52+
settings, err := runner.LoadSettings()
53+
if err != nil {
54+
runner.Logln("Could not load settings, assuming defaults.")
55+
settings = runner.DefaultSettings
56+
}
57+
58+
ec := runner.NewEvalContext(*settings)
59+
60+
err = ec.Eval(projectId, panelId)
61+
if err != nil {
62+
runner.Logln("Failed to eval: %s", err)
63+
64+
if _, ok := err.(*runner.DSError); !ok {
65+
err = runner.Edse(err)
66+
err.(*runner.DSError).Stack = "Unknown"
67+
}
68+
69+
err := runner.WriteJSONFile(panelMetaOut, map[string]interface{}{
70+
"exception": err,
71+
})
72+
if err != nil {
73+
runner.Fatalln("Could not write panel meta out: %s", err)
74+
}
75+
76+
// Explicitly don't fail here so that the parent can read the exception from disk
77+
}
78+
}
File renamed without changes.

runner/database.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package runner
22

33
import (
44
"encoding/base64"
@@ -200,6 +200,13 @@ func writeRowFromDatabase(dbInfo DatabaseConnectorInfoDatabase, w *JSONArrayWrit
200200
return err
201201
}
202202

203+
// Needing this whole translation layer may be a good reason
204+
// not to use sqlx since it translates **into** this layer
205+
// from being raw. At this point we're just reimplementing
206+
// sqlx in reverse on top of sqlx. Might be better to do
207+
// reflection directly on the sql package instead. Would be
208+
// worth benchmarking.
209+
203210
// The MySQL driver is not friendly about unknown data types.
204211
// https://github.com/go-sql-driver/mysql/issues/441
205212
for _, s := range colTypes {
@@ -234,7 +241,7 @@ func writeRowFromDatabase(dbInfo DatabaseConnectorInfoDatabase, w *JSONArrayWrit
234241
// Default to treating everything as a string
235242
row[col] = string(bs)
236243
if !wroteFirstRow && !textTypes[t] {
237-
logln("Skipping unknown type: " + s.DatabaseTypeName())
244+
Logln("Skipping unknown type: " + s.DatabaseTypeName())
238245
}
239246
}
240247
}
@@ -249,7 +256,7 @@ func writeRowFromDatabase(dbInfo DatabaseConnectorInfoDatabase, w *JSONArrayWrit
249256

250257
}
251258

252-
func evalDatabasePanel(project *ProjectState, pageIndex int, panel *PanelInfo) error {
259+
func EvalDatabasePanel(project *ProjectState, pageIndex int, panel *PanelInfo, panelResultLoader func(string, string, interface{}) error) error {
253260
var connector *ConnectorInfo
254261
for _, c := range project.Connectors {
255262
cc := c
@@ -339,11 +346,23 @@ func evalDatabasePanel(project *ProjectState, pageIndex int, panel *PanelInfo) e
339346
return err
340347
}
341348

342-
return withRemoteConnection(server, host, port, func(host, port string) error {
343-
out := getPanelResultsFile(project.Id, panel.Id)
349+
if panelResultLoader == nil {
350+
panelResultLoader = func(projectId, panelId string, res interface{}) error {
351+
f := GetPanelResultsFile(projectId, panelId)
352+
return readJSONFileInto(f, res)
353+
}
354+
}
344355

356+
out := GetPanelResultsFile(project.Id, panel.Id)
357+
w, err := openTruncate(out)
358+
if err != nil {
359+
return err
360+
}
361+
defer w.Close()
362+
363+
return withRemoteConnection(server, host, port, func(host, port string) error {
345364
wroteFirstRow := false
346-
return withJSONArrayOutWriterFile(out, func(w *JSONArrayWriter) error {
365+
return withJSONArrayOutWriterFile(w, func(w *JSONArrayWriter) error {
347366
_, err := importAndRun(
348367
func(createTableStmt string) error {
349368
_, err := db.Exec(createTableStmt)
@@ -377,6 +396,7 @@ func evalDatabasePanel(project *ProjectState, pageIndex int, panel *PanelInfo) e
377396
panelsToImport,
378397
qt,
379398
mangleInsert,
399+
panelResultLoader,
380400
)
381401

382402
return err

runner/database_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package runner
22

33
import (
44
"testing"

runner/errors.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package runner
22

33
import (
44
"encoding/json"
@@ -60,6 +60,7 @@ func makeErrException(e error) *DSError {
6060
}
6161

6262
var edse = makeErrException
63+
var Edse = edse
6364

6465
func edsef(msg string, args ...interface{}) *DSError {
6566
return edse(fmt.Errorf(msg, args...))

0 commit comments

Comments
 (0)