Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
39 changes: 28 additions & 11 deletions plugins/sql/pkg/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
"math"

_ "github.com/ClickHouse/clickhouse-go"
_ "github.com/go-sql-driver/mysql"
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions plugins/sql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 6 additions & 3 deletions plugins/sql/src/components/panel/SQLTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -20,11 +21,13 @@ const SQLTable: React.FunctionComponent<ISQLTableProps> = ({ rows, columns }: IS
</Tr>
</Thead>
<Tbody>
{rows
{rows && rows.length > 0
? rows.map((row, rowIndex) => (
<Tr key={rowIndex}>
{row.map((column, columnIndex) => (
<Td key={`${rowIndex}_${columnIndex}`}>{column}</Td>
{columns.map((column, columnIndex) => (
<Td key={`${rowIndex}_${columnIndex}`}>
{row.hasOwnProperty(column) ? renderCellValue(row[column]) : ''}
</Td>
))}
</Tr>
))
Expand Down
8 changes: 8 additions & 0 deletions plugins/sql/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
};
6 changes: 5 additions & 1 deletion plugins/sql/src/utils/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
}