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