* Moving things around
* Copy parseURL function to where it's used
* Update test
* Remove experimental-strip-types
* Revert "Remove experimental-strip-types"
This reverts commit 70fbc1c0cd.
* Trigger build
175 lines
5.0 KiB
Go
175 lines
5.0 KiB
Go
package utils
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"reflect"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/grafana/grafana-azure-sdk-go/v2/azcredentials"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/grafana/grafana-plugin-sdk-go/data/sqlutil"
|
|
mssql "github.com/microsoft/go-mssqldb"
|
|
)
|
|
|
|
// GetJsonData just gets the json in easier to work with type. It's used on multiple places which isn't super effective
|
|
// but only when creating a client which should not happen often anyway.
|
|
func getJsonData(settings backend.DataSourceInstanceSettings) (map[string]interface{}, error) {
|
|
var jsonData map[string]interface{}
|
|
err := json.Unmarshal(settings.JSONData, &jsonData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error unmarshalling JSONData: %w", err)
|
|
}
|
|
return jsonData, nil
|
|
}
|
|
|
|
func GetAzureCredentials(settings backend.DataSourceInstanceSettings) (azcredentials.AzureCredentials, error) {
|
|
jsonData, err := getJsonData(settings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return azcredentials.FromDatasourceData(jsonData, settings.DecryptedSecureJSONData)
|
|
}
|
|
|
|
type DebugOnlyLogger interface {
|
|
Debug(msg string, args ...interface{})
|
|
}
|
|
|
|
// ParseURL tries to parse an MSSQL URL string into a URL object.
|
|
func ParseURL(u string, logger DebugOnlyLogger) (*url.URL, error) {
|
|
logger.Debug("Parsing MSSQL URL", "url", u)
|
|
|
|
// Recognize ODBC connection strings like host\instance:1234
|
|
reODBC := regexp.MustCompile(`^[^\\:]+(?:\\[^:]+)?(?::\d+)?(?:;.+)?$`)
|
|
var host string
|
|
switch {
|
|
case reODBC.MatchString(u):
|
|
logger.Debug("Recognized as ODBC URL format", "url", u)
|
|
host = u
|
|
default:
|
|
logger.Debug("Couldn't recognize as valid MSSQL URL", "url", u)
|
|
return nil, fmt.Errorf("unrecognized MSSQL URL format: %q", u)
|
|
}
|
|
return &url.URL{
|
|
Scheme: "sqlserver",
|
|
Host: host,
|
|
}, nil
|
|
}
|
|
|
|
type MSSQLQueryResultTransformer struct {
|
|
UserError string
|
|
}
|
|
|
|
func (t *MSSQLQueryResultTransformer) TransformQueryError(logger log.Logger, err error) error {
|
|
// go-mssql overrides source error, so we currently match on string
|
|
// ref https://github.com/denisenkom/go-mssqldb/blob/045585d74f9069afe2e115b6235eb043c8047043/tds.go#L904
|
|
if strings.HasPrefix(strings.ToLower(err.Error()), "unable to open tcp connection with host") {
|
|
logger.Error("Query error", "error", err)
|
|
return fmt.Errorf("failed to connect to server - %s", t.UserError)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (t *MSSQLQueryResultTransformer) GetConverterList() []sqlutil.StringConverter {
|
|
return []sqlutil.StringConverter{
|
|
{
|
|
Name: "handle MONEY",
|
|
InputScanKind: reflect.Slice,
|
|
InputTypeName: "MONEY",
|
|
ConversionFunc: func(in *string) (*string, error) { return in, nil },
|
|
Replacer: &sqlutil.StringFieldReplacer{
|
|
OutputFieldType: data.FieldTypeNullableFloat64,
|
|
ReplaceFunc: func(in *string) (any, error) {
|
|
if in == nil {
|
|
return nil, nil
|
|
}
|
|
v, err := strconv.ParseFloat(*in, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &v, nil
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "handle SMALLMONEY",
|
|
InputScanKind: reflect.Slice,
|
|
InputTypeName: "SMALLMONEY",
|
|
ConversionFunc: func(in *string) (*string, error) { return in, nil },
|
|
Replacer: &sqlutil.StringFieldReplacer{
|
|
OutputFieldType: data.FieldTypeNullableFloat64,
|
|
ReplaceFunc: func(in *string) (any, error) {
|
|
if in == nil {
|
|
return nil, nil
|
|
}
|
|
v, err := strconv.ParseFloat(*in, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &v, nil
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "handle DECIMAL",
|
|
InputScanKind: reflect.Slice,
|
|
InputTypeName: "DECIMAL",
|
|
ConversionFunc: func(in *string) (*string, error) { return in, nil },
|
|
Replacer: &sqlutil.StringFieldReplacer{
|
|
OutputFieldType: data.FieldTypeNullableFloat64,
|
|
ReplaceFunc: func(in *string) (any, error) {
|
|
if in == nil {
|
|
return nil, nil
|
|
}
|
|
v, err := strconv.ParseFloat(*in, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &v, nil
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "handle UNIQUEIDENTIFIER",
|
|
InputScanKind: reflect.Slice,
|
|
InputTypeName: "UNIQUEIDENTIFIER",
|
|
ConversionFunc: func(in *string) (*string, error) { return in, nil },
|
|
Replacer: &sqlutil.StringFieldReplacer{
|
|
OutputFieldType: data.FieldTypeNullableString,
|
|
ReplaceFunc: func(in *string) (any, error) {
|
|
if in == nil {
|
|
return nil, nil
|
|
}
|
|
uuid := &mssql.UniqueIdentifier{}
|
|
if err := uuid.Scan([]byte(*in)); err != nil {
|
|
return nil, err
|
|
}
|
|
v := uuid.String()
|
|
return &v, nil
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "handle SQL_VARIANT",
|
|
InputScanKind: reflect.Pointer,
|
|
InputTypeName: "SQL_VARIANT",
|
|
ConversionFunc: func(in *string) (*string, error) { return in, nil },
|
|
Replacer: &sqlutil.StringFieldReplacer{
|
|
OutputFieldType: data.FieldTypeNullableString,
|
|
ReplaceFunc: func(in *string) (any, error) {
|
|
if in == nil {
|
|
return nil, nil
|
|
}
|
|
return in, nil
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|