diff --git a/CHANGELOG.md b/CHANGELOG.md index 663f6b201..eb343bf37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan - [#179](https://github.com/kobsio/kobs/pull/179): [clickhouse] Add sorting and show milliseconds in time column. - [#181](https://github.com/kobsio/kobs/pull/181): [core] :warning: _Breaking change:_ :warning: Make the time selection across all plugins more intuitive. For that we removed the `time` property from the Options component and showing the formatted timestamp instead. - [#185](https://github.com/kobsio/kobs/pull/185): [clickhouse] Use pagination instead of Intersection Observer API to display logs. +- [#188](https://github.com/kobsio/kobs/pull/188): [sql] Replace the `GetQueryResults` function with the implemention used in the Clickhouse plugin, to have a proper handling for float values. ## [v0.6.0](https://github.com/kobsio/kobs/releases/tag/v0.6.0) (2021-10-11) diff --git a/plugins/sql/pkg/instance/instance.go b/plugins/sql/pkg/instance/instance.go index 6ac4c539b..49fcc8910 100644 --- a/plugins/sql/pkg/instance/instance.go +++ b/plugins/sql/pkg/instance/instance.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "math" _ "github.com/ClickHouse/clickhouse-go" _ "github.com/go-sql-driver/mysql" @@ -31,35 +32,51 @@ type Instance struct { } // GetQueryResults returns all rows for the user provided SQL query. -func (i *Instance) GetQueryResults(ctx context.Context, query string) ([][]interface{}, []string, error) { +func (i *Instance) GetQueryResults(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { rows, err := i.client.QueryContext(ctx, query) if err != nil { return nil, nil, err } defer rows.Close() - var columns []string - columns, err = rows.Columns() + columns, err := rows.Columns() if err != nil { return nil, nil, err } - columnsLen := len(columns) - var result [][]interface{} + var result []map[string]interface{} for rows.Next() { - var r []interface{} - r = make([]interface{}, columnsLen) + values := make([]interface{}, len(columns)) + pointers := make([]interface{}, len(columns)) - for i := 0; i < columnsLen; i++ { - r[i] = new(interface{}) + for i := range values { + pointers[i] = &values[i] } - if err := rows.Scan(r...); err != nil { + if err := rows.Scan(pointers...); err != nil { return nil, nil, err } - result = append(result, r) + // When we assign the correct value to an row, we also have to check if the returned value is of type float and + // if the value is NaN or Inf, because then the json encoding would fail if we add the value. + resultMap := make(map[string]interface{}) + for i, val := range values { + switch v := val.(type) { + case float64: + if !math.IsNaN(v) && !math.IsInf(v, 0) { + resultMap[columns[i]] = val + } + default: + resultMap[columns[i]] = val + } + } + + result = append(result, resultMap) + } + + if err := rows.Err(); err != nil { + return nil, nil, err } return result, columns, nil diff --git a/plugins/sql/sql.go b/plugins/sql/sql.go index 3d3d33062..8e9a31d74 100644 --- a/plugins/sql/sql.go +++ b/plugins/sql/sql.go @@ -59,8 +59,8 @@ func (router *Router) getQueryResults(w http.ResponseWriter, r *http.Request) { } data := struct { - Rows [][]interface{} `json:"rows"` - Columns []string `json:"columns"` + Rows []map[string]interface{} `json:"rows"` + Columns []string `json:"columns"` }{ rows, columns, diff --git a/plugins/sql/src/components/panel/SQLTable.tsx b/plugins/sql/src/components/panel/SQLTable.tsx index f50e3f200..4b55e5099 100644 --- a/plugins/sql/src/components/panel/SQLTable.tsx +++ b/plugins/sql/src/components/panel/SQLTable.tsx @@ -2,6 +2,7 @@ import { TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patter import React from 'react'; import { ISQLData } from '../../utils/interfaces'; +import { renderCellValue } from '../../utils/helpers'; type ISQLTableProps = ISQLData; @@ -20,11 +21,13 @@ const SQLTable: React.FunctionComponent = ({ rows, columns }: IS - {rows + {rows && rows.length > 0 ? rows.map((row, rowIndex) => ( - {row.map((column, columnIndex) => ( - {column} + {columns.map((column, columnIndex) => ( + + {row.hasOwnProperty(column) ? renderCellValue(row[column]) : ''} + ))} )) diff --git a/plugins/sql/src/utils/helpers.ts b/plugins/sql/src/utils/helpers.ts index 0b056f9e4..3235cd89a 100644 --- a/plugins/sql/src/utils/helpers.ts +++ b/plugins/sql/src/utils/helpers.ts @@ -4,3 +4,11 @@ export const getQueryFromSearch = (search: string): string => { const query = params.get('query'); return query ? query : ''; }; + +export const renderCellValue = (value: string | number | string[] | number[]): string => { + if (Array.isArray(value)) { + return `[${value.join(', ')}]`; + } + + return `${value}`; +}; diff --git a/plugins/sql/src/utils/interfaces.ts b/plugins/sql/src/utils/interfaces.ts index 6cc27f970..505edc657 100644 --- a/plugins/sql/src/utils/interfaces.ts +++ b/plugins/sql/src/utils/interfaces.ts @@ -17,5 +17,9 @@ export interface IQuery { // ISQLData is the interface of the data returned from our Go API for the get query results call. export interface ISQLData { columns?: string[]; - rows?: string[][]; + rows?: ISQLDataRow[]; +} + +export interface ISQLDataRow { + [key: string]: string | number | string[] | number[]; }