1package sqlutil
2
3import (
4	"database/sql"
5	"fmt"
6	"reflect"
7	"time"
8
9	"github.com/grafana/grafana-plugin-sdk-go/data"
10)
11
12// FrameConverter defines how to convert the scanned value into a value that can be put into a dataframe (OutputFieldType)
13type FrameConverter struct {
14	// FieldType is the type that is created for the dataframe field.
15	// The returned value from `ConverterFunc` should match this type, otherwise the data package will panic.
16	FieldType data.FieldType
17	// ConverterFunc defines how to convert the scanned `InputScanType` to the supplied `FieldType`.
18	// `in` is always supplied as a pointer, as it is scanned as a pointer, even if `InputScanType` is not a pointer.
19	// For example, if `InputScanType` is `string`, then `in` is `*string`
20	ConverterFunc func(in interface{}) (interface{}, error)
21}
22
23// StringConverter can be used to store types not supported by
24// a Frame into a *string. When scanning, if a SQL's row's InputScanType's Kind
25// and InputScanKind match that returned by the sql response, then the
26// conversion func will be run on the row.
27// Note, a Converter should be favored over a StringConverter as not all SQL rows can be scanned into a string.
28// This type is only here for backwards compatibility.
29type StringConverter struct {
30	// Name is an optional property that can be used to identify a converter
31	Name          string
32	InputScanKind reflect.Kind // reflect.Type might better or worse option?
33	InputTypeName string
34
35	// Conversion func may be nil to do no additional operations on the string conversion.
36	ConversionFunc func(in *string) (*string, error)
37
38	// If the Replacer is not nil, the replacement will be performed.
39	Replacer *StringFieldReplacer
40}
41
42// Note: StringConverter is perhaps better understood as []byte. However, currently
43// the Vector type ([][]byte) is not supported. https://github.com/grafana/grafana-plugin-sdk-go/issues/57
44
45// StringFieldReplacer is used to replace a *string Field in a Frame. The type
46// returned by the ReplaceFunc must match the type of elements of VectorType.
47// Both properties must be non-nil.
48// Note, a Converter should be favored over a StringConverter as not all SQL rows can be scanned into a string.
49// This type is only here for backwards compatibility.
50type StringFieldReplacer struct {
51	OutputFieldType data.FieldType
52	ReplaceFunc     func(in *string) (interface{}, error)
53}
54
55// ToConverter turns this StringConverter into a Converter, using the ScanType of string
56func (s StringConverter) ToConverter() Converter {
57	return Converter{
58		Name:           s.Name,
59		InputScanType:  reflect.TypeOf(sql.NullString{}),
60		InputTypeName:  s.InputTypeName,
61		FrameConverter: StringFrameConverter(s),
62	}
63}
64
65// StringFrameConverter creates a FrameConverter from a StringConverter
66func StringFrameConverter(s StringConverter) FrameConverter {
67	f := data.FieldTypeNullableString
68
69	if s.Replacer != nil {
70		f = s.Replacer.OutputFieldType
71	}
72
73	return FrameConverter{
74		FieldType: f,
75		ConverterFunc: func(in interface{}) (interface{}, error) {
76			ns := in.(*sql.NullString)
77			if !ns.Valid {
78				return nil, nil
79			}
80
81			v := &ns.String
82			if s.ConversionFunc != nil {
83				converted, err := s.ConversionFunc(v)
84				if err != nil {
85					return nil, err
86				}
87				v = converted
88			}
89
90			if s.Replacer.ReplaceFunc != nil {
91				return s.Replacer.ReplaceFunc(v)
92			}
93
94			return v, nil
95		},
96	}
97}
98
99// ToConverters creates a slice of Converters from a slice of StringConverters
100func ToConverters(s ...StringConverter) []Converter {
101	n := make([]Converter, len(s))
102	for i, v := range s {
103		n[i] = v.ToConverter()
104	}
105
106	return n
107}
108
109// Converter is used to convert known types returned in sql.Row to a type usable in a dataframe.
110type Converter struct {
111	// Name is the name of the converter that is used to distinguish them when debugging or parsing log output
112	Name string
113
114	// InputScanType is the type that is used when (*sql.Rows).Scan(...) is called.
115	// Some drivers require certain data types to be used when scanning data from sql rows, and this type should reflect that.
116	InputScanType reflect.Type
117
118	// InputTypeName is the case-sensitive name that must match the type that this converter matches
119	InputTypeName string
120
121	// FrameConverter defines how to convert the scanned value into a value that can be put into a dataframe
122	FrameConverter FrameConverter
123}
124
125// DefaultConverterFunc assumes that the scanned value, in, is already a type that can be put into a dataframe.
126func DefaultConverterFunc(t reflect.Type) func(in interface{}) (interface{}, error) {
127	return func(in interface{}) (interface{}, error) {
128		inType := reflect.TypeOf(in)
129		if inType == reflect.PtrTo(t) {
130			n := reflect.ValueOf(in)
131
132			return n.Elem().Interface(), nil
133		}
134
135		return in, nil
136	}
137}
138
139// NewDefaultConverter creates a Converter that assumes that the value is scannable into a String, and placed into the dataframe as a nullable string.
140func NewDefaultConverter(name string, nullable bool, t reflect.Type) Converter {
141	slice := reflect.MakeSlice(reflect.SliceOf(t), 0, 0).Interface()
142	if !data.ValidFieldType(slice) {
143		return Converter{
144			Name:          fmt.Sprintf("[%s] String converter", t),
145			InputScanType: reflect.TypeOf(sql.NullString{}),
146			InputTypeName: name,
147			FrameConverter: FrameConverter{
148				FieldType: data.FieldTypeNullableString,
149				ConverterFunc: func(in interface{}) (interface{}, error) {
150					v := in.(*sql.NullString)
151
152					if !v.Valid {
153						return (*string)(nil), nil
154					}
155
156					f := v.String
157					return &f, nil
158				},
159			},
160		}
161	}
162
163	v := reflect.New(t)
164	var fieldType data.FieldType
165	if v.Type() == reflect.PtrTo(t) {
166		v = v.Elem()
167		fieldType = data.FieldTypeFor(v.Interface())
168	} else {
169		fieldType = data.FieldTypeFor(v.Interface()).NullableType()
170	}
171
172	if nullable {
173		if converter, ok := NullConverters[t]; ok {
174			return converter
175		}
176	}
177
178	return Converter{
179		Name:          fmt.Sprintf("Default converter for %s", name),
180		InputScanType: t,
181		InputTypeName: name,
182		FrameConverter: FrameConverter{
183			FieldType:     fieldType,
184			ConverterFunc: DefaultConverterFunc(t),
185		},
186	}
187}
188
189var (
190	// NullStringConverter creates a *string using the scan type of `sql.NullString`
191	NullStringConverter = Converter{
192		Name:          "nullable string converter",
193		InputScanType: reflect.TypeOf(sql.NullString{}),
194		InputTypeName: "STRING",
195		FrameConverter: FrameConverter{
196			FieldType: data.FieldTypeNullableString,
197			ConverterFunc: func(n interface{}) (interface{}, error) {
198				v := n.(*sql.NullString)
199
200				if !v.Valid {
201					return (*string)(nil), nil
202				}
203
204				f := v.String
205				return &f, nil
206			},
207		},
208	}
209
210	// NullDecimalConverter creates a *float64 using the scan type of `sql.NullFloat64`
211	NullDecimalConverter = Converter{
212		Name:          "NULLABLE decimal converter",
213		InputScanType: reflect.TypeOf(sql.NullFloat64{}),
214		InputTypeName: "DOUBLE",
215		FrameConverter: FrameConverter{
216			FieldType: data.FieldTypeNullableFloat64,
217			ConverterFunc: func(n interface{}) (interface{}, error) {
218				v := n.(*sql.NullFloat64)
219
220				if !v.Valid {
221					return (*float64)(nil), nil
222				}
223
224				f := v.Float64
225				return &f, nil
226			},
227		},
228	}
229
230	// NullInt64Converter creates a *int64 using the scan type of `sql.NullInt64`
231	NullInt64Converter = Converter{
232		Name:          "NULLABLE int64 converter",
233		InputScanType: reflect.TypeOf(sql.NullInt64{}),
234		InputTypeName: "INTEGER",
235		FrameConverter: FrameConverter{
236			FieldType: data.FieldTypeNullableInt64,
237			ConverterFunc: func(n interface{}) (interface{}, error) {
238				v := n.(*sql.NullInt64)
239
240				if !v.Valid {
241					return (*int64)(nil), nil
242				}
243
244				f := v.Int64
245				return &f, nil
246			},
247		},
248	}
249
250	// NullInt32Converter creates a *int32 using the scan type of `sql.NullInt32`
251	NullInt32Converter = Converter{
252		Name:          "NULLABLE int32 converter",
253		InputScanType: reflect.TypeOf(sql.NullInt32{}),
254		InputTypeName: "INTEGER",
255		FrameConverter: FrameConverter{
256			FieldType: data.FieldTypeNullableInt32,
257			ConverterFunc: func(n interface{}) (interface{}, error) {
258				v := n.(*sql.NullInt32)
259
260				if !v.Valid {
261					return (*int32)(nil), nil
262				}
263
264				f := v.Int32
265				return &f, nil
266			},
267		},
268	}
269
270	// NullTimeConverter creates a *time.time using the scan type of `sql.NullTime`
271	NullTimeConverter = Converter{
272		Name:          "NULLABLE time.Time converter",
273		InputScanType: reflect.TypeOf(sql.NullTime{}),
274		InputTypeName: "TIMESTAMP",
275		FrameConverter: FrameConverter{
276			FieldType: data.FieldTypeNullableTime,
277			ConverterFunc: func(n interface{}) (interface{}, error) {
278				v := n.(*sql.NullTime)
279
280				if !v.Valid {
281					return (*time.Time)(nil), nil
282				}
283
284				f := v.Time
285				return &f, nil
286			},
287		},
288	}
289
290	// NullBoolConverter creates a *bool using the scan type of `sql.NullBool`
291	NullBoolConverter = Converter{
292		Name:          "nullable bool converter",
293		InputScanType: reflect.TypeOf(sql.NullBool{}),
294		InputTypeName: "BOOLEAN",
295		FrameConverter: FrameConverter{
296			FieldType: data.FieldTypeNullableBool,
297			ConverterFunc: func(n interface{}) (interface{}, error) {
298				v := n.(*sql.NullBool)
299
300				if !v.Valid {
301					return (*bool)(nil), nil
302				}
303
304				return &v.Bool, nil
305			},
306		},
307	}
308)
309
310// NullConverters is a map of data type names (from reflect.TypeOf(...).String()) to converters
311// Converters supplied here are used as defaults for fields that do not have a supplied Converter
312var NullConverters = map[reflect.Type]Converter{
313	reflect.TypeOf(float64(0)):  NullDecimalConverter,
314	reflect.TypeOf(int64(0)):    NullInt64Converter,
315	reflect.TypeOf(int32(0)):    NullInt32Converter,
316	reflect.TypeOf(""):          NullStringConverter,
317	reflect.TypeOf(time.Time{}): NullTimeConverter,
318	reflect.TypeOf(false):       NullBoolConverter,
319}
320