diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d63e78..abc3c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -## 1.0.0 (Unreleased) +## 0.1.0 (Unreleased) Initial release. diff --git a/cmd/preinstall-extensions/go.mod b/cmd/preinstall-extensions/go.mod new file mode 100644 index 0000000..faf5859 --- /dev/null +++ b/cmd/preinstall-extensions/go.mod @@ -0,0 +1,23 @@ +module github.com/motherduckdb/grafana-duckdb-datasource/cmd/preinstall-extensions + +go 1.23 + +require github.com/marcboeker/go-duckdb v1.8.3 + +require ( + github.com/apache/arrow/go/v17 v17.0.0 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/google/flatbuffers v24.3.25+incompatible // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/tools v0.26.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + gonum.org/v1/gonum v0.15.1 // indirect +) diff --git a/cmd/preinstall-extensions/go.sum b/cmd/preinstall-extensions/go.sum new file mode 100644 index 0000000..5076e71 --- /dev/null +++ b/cmd/preinstall-extensions/go.sum @@ -0,0 +1,45 @@ +github.com/apache/arrow/go/v17 v17.0.0 h1:RRR2bdqKcdbss9Gxy2NS/hK8i4LDMh23L6BbkN5+F54= +github.com/apache/arrow/go/v17 v17.0.0/go.mod h1:jR7QHkODl15PfYyjM2nU+yTLScZ/qfj7OSUZmJ8putc= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/marcboeker/go-duckdb v1.8.0 h1:iOWv1wTL0JIMqpyns6hCf5XJJI4fY6lmJNk+itx5RRo= +github.com/marcboeker/go-duckdb v1.8.0/go.mod h1:2oV8BZv88S16TKGKM+Lwd0g7DX84x0jMxjTInThC8Is= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/preinstall-extensions/main.go b/cmd/preinstall-extensions/main.go new file mode 100644 index 0000000..de92b10 --- /dev/null +++ b/cmd/preinstall-extensions/main.go @@ -0,0 +1,73 @@ +// From https://github.com/snakedotdev/grafana-duckdb-datasource +// Apache 2.0 Licensed +// Copyright snakedotdev +// Modified from original version + +package main + +import ( + "database/sql" + "flag" + "fmt" + "log" + "path/filepath" + "strings" + + _ "github.com/marcboeker/go-duckdb" +) + +func main() { + var ( + sqlCmd = flag.String("s", "", "SQL command to execute (multiple commands can be separated by semicolons)") + dbPath = flag.String("db", "", "Path to DuckDB database file (empty for in-memory)") + ) + + flag.Parse() + + if *sqlCmd == "" { + log.Fatal("SQL command is required") + } + + // Prepare database path + dsn := "?access_mode=read_write" + if *dbPath != "" { + absPath, err := filepath.Abs(*dbPath) + if err != nil { + log.Fatalf("Failed to get absolute path: %v", err) + } + dsn = absPath + dsn + } + + fmt.Printf("Using DSN: %s\n", dsn) + + // Open database connection + db, err := sql.Open("duckdb", dsn) + if err != nil { + log.Fatalf("Failed to open database: %v", err) + } + defer db.Close() + + // Split the input command into individual commands + commands := strings.Split(*sqlCmd, ";") + + // Execute each command + for _, cmd := range commands { + // Skip empty commands + cmd = strings.TrimSpace(cmd) + if cmd == "" { + continue + } + + fmt.Printf("Executing: %s\n", cmd) + result, err := db.Exec(cmd) + if err != nil { + log.Fatalf("Failed to execute SQL '%s': %v", cmd, err) + } + if result != nil { + rows, _ := result.RowsAffected() + fmt.Printf("Rows affected: %d\n", rows) + } + } + + fmt.Println("All commands executed successfully") +} diff --git a/go.mod b/go.mod index 0579446..cc0350c 100644 --- a/go.mod +++ b/go.mod @@ -93,13 +93,13 @@ require ( go.opentelemetry.io/otel/sdk v1.33.0 // indirect go.opentelemetry.io/otel/trace v1.33.0 // indirect go.opentelemetry.io/proto/otlp v1.4.0 // indirect - golang.org/x/crypto v0.31.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.32.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.28.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect diff --git a/go.sum b/go.sum index 7ac84ac..b1252ef 100644 --- a/go.sum +++ b/go.sum @@ -268,8 +268,8 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -281,8 +281,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -300,10 +300,10 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= diff --git a/package.json b/package.json index 693f73b..1abfda7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "duckdb-datasource", - "version": "1.0.0", - "description": "Duck db and mother duck datasource for grafana", + "version": "0.1.0", + "description": "DuckDB and MotherDuck datasource for grafana", "scripts": { "build": "webpack -c ./.config/webpack/webpack.config.ts --env production", "dev": "webpack -w -c ./.config/webpack/webpack.config.ts --env development", diff --git a/pkg/models/settings.go b/pkg/models/settings.go index a1bde0b..0a6c813 100644 --- a/pkg/models/settings.go +++ b/pkg/models/settings.go @@ -9,6 +9,7 @@ import ( type PluginSettings struct { Path string `json:"path"` + InitSql string `json:"initSql"` Secrets *SecretPluginSettings `json:"-"` } @@ -22,7 +23,6 @@ func LoadPluginSettings(source backend.DataSourceInstanceSettings) (*PluginSetti if err != nil { return nil, fmt.Errorf("could not unmarshal PluginSettings json: %w", err) } - settings.Secrets = loadSecretPluginSettings(source.DecryptedSecureJSONData) return &settings, nil @@ -30,6 +30,6 @@ func LoadPluginSettings(source backend.DataSourceInstanceSettings) (*PluginSetti func loadSecretPluginSettings(source map[string]string) *SecretPluginSettings { return &SecretPluginSettings{ - MotherDuckToken: source["motherduckToken"], + MotherDuckToken: source["motherDuckToken"], } } diff --git a/pkg/plugin/duckdb_driver.go b/pkg/plugin/duckdb_driver.go index c4d1aad..c3c9429 100644 --- a/pkg/plugin/duckdb_driver.go +++ b/pkg/plugin/duckdb_driver.go @@ -57,50 +57,24 @@ func (d *DuckDBDriver) Connect(ctx context.Context, settings backend.DataSourceI os.Setenv("motherduck_token", config.Secrets.MotherDuckToken) } - // // join config as url parmaeters - // config, err := parseConfig(settings) - - // if err != nil { - // return nil, err - // } - - // // Create a slice to hold the URL-encoded key-value pairs - // var parts []string - // var dbPath string - // // Iterate through the map - // for key, value := range config { - // if key == "path" { - // dbPath = value - // continue - // } else if key == "apiKey" { - // } - - // // URL-encode the key and the value - // encodedKey := url.QueryEscape(key) - // encodedValue := url.QueryEscape(value) - - // // Append the encoded key-value pair to the slice - // parts = append(parts, fmt.Sprintf("%s=%s", encodedKey, encodedValue)) - // } - - // Join all parts with '&' to form the final query string - // queryString := strings.Join(parts, "&") - // dbString := strings.Join([]string{dbPath, queryString}, "?") - connector, err := duckdb.NewConnector(config.Path, func(execer driver.ExecerContext) error { - bootQueries := []string{} if strings.HasPrefix(config.Path, "md:") { bootQueries = append(bootQueries, "INSTALL 'motherduck';", "LOAD 'motherduck';") } - // read env variable GF_PATHS_HOME + // read env variable GF_PATHS_HOME and use it as the home directory for extension installation. homePath := os.Getenv("GF_PATHS_HOME") if homePath != "" { bootQueries = append(bootQueries, "SET home_directory='"+homePath+"';") } + // User defined init queries. + if strings.TrimSpace(config.InitSql) != "" { + bootQueries = append(bootQueries, config.InitSql) + } + for _, query := range bootQueries { _, err = execer.ExecContext(context.Background(), query, nil) if err != nil { diff --git a/src/components/ConfigEditor.tsx b/src/components/ConfigEditor.tsx index 2532f45..4250b59 100644 --- a/src/components/ConfigEditor.tsx +++ b/src/components/ConfigEditor.tsx @@ -1,5 +1,5 @@ import React, { ChangeEvent } from 'react'; -import { InlineField, Input, SecretInput } from '@grafana/ui'; +import { InlineField, Input, SecretInput, TextArea } from '@grafana/ui'; import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; import { DuckDBDataSourceOptions, SecureJsonData } from '../types'; @@ -19,6 +19,17 @@ export function ConfigEditor(props: Props) { }); }; + const onInitSqlChange = (event: ChangeEvent) => { + onOptionsChange({ + ...options, + jsonData: { + ...jsonData, + initSql: event.target.value, + }, + }); + }; + + // Secure field (only sent to the backend) const onMotherDuckTokenChange = (event: ChangeEvent) => { onOptionsChange({ @@ -33,11 +44,9 @@ export function ConfigEditor(props: Props) { onOptionsChange({ ...options, secureJsonFields: { - ...options.secureJsonFields, motherDuckToken: false, }, secureJsonData: { - ...options.secureJsonData, motherDuckToken: '', }, }); @@ -45,16 +54,27 @@ export function ConfigEditor(props: Props) { return ( <> - + - + +