parent
bd490bc18f
commit
b2d5332f67
@ -0,0 +1,69 @@
|
||||
package themis
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FormatRows(rows *sql.Rows) (string, error) {
|
||||
sb := strings.Builder{}
|
||||
|
||||
cols, err := rows.Columns()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get rows columns: %w", err)
|
||||
}
|
||||
|
||||
c := make([]string, len(cols))
|
||||
for i := range c {
|
||||
c[i] = " %-*s "
|
||||
}
|
||||
pattern := fmt.Sprintf("|%s|\n", strings.Join(c, "|"))
|
||||
|
||||
lengths := make([]int, len(cols))
|
||||
for i := range lengths {
|
||||
lengths[i] = len(cols[i])
|
||||
}
|
||||
|
||||
scanned := make([][]any, 0)
|
||||
for rows.Next() {
|
||||
row := make([]interface{}, len(cols))
|
||||
for i := range row {
|
||||
row[i] = new(string)
|
||||
}
|
||||
rows.Scan(row...)
|
||||
scanned = append(scanned, row) // keep track of row for later
|
||||
for i, a := range row {
|
||||
s := a.(*string)
|
||||
if len(*s) > lengths[i] {
|
||||
lengths[i] = len(*s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write column names
|
||||
curr := make([]any, 0, 2*len(cols))
|
||||
for i := range lengths {
|
||||
curr = append(curr, lengths[i], cols[i])
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(pattern, curr...))
|
||||
|
||||
// Write header separator row
|
||||
curr = curr[:0] // empty slice but preserve capacity
|
||||
for i := range lengths {
|
||||
curr = append(curr, lengths[i], strings.Repeat("-", lengths[i]))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(pattern, curr...))
|
||||
|
||||
// iterate rows and write each one
|
||||
for _, r := range scanned {
|
||||
curr = curr[:0] // empty slice but preserve capacity
|
||||
for i := range lengths {
|
||||
s := r[i].(*string)
|
||||
curr = append(curr, lengths[i], *s)
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(pattern, curr...))
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package themis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFormatRows(t *testing.T) {
|
||||
store, err := NewStore(fmt.Sprintf(TEST_CONN_STRING_PATTERN, "format-rows"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
rows, err := store.db.Query("SELECT provinces.name, provinces.region, provinces.area, provinces.trade_node FROM provinces WHERE area = 'Gascony'")
|
||||
assert.NoError(t, err)
|
||||
|
||||
fmtd, err := FormatRows(rows)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `| name | region | area | trade_node |
|
||||
| -------- | ------ | ------- | ---------- |
|
||||
| Labourd | France | Gascony | Bordeaux |
|
||||
| Armagnac | France | Gascony | Bordeaux |
|
||||
| Béarn | France | Gascony | Bordeaux |
|
||||
| Foix | France | Gascony | Bordeaux |
|
||||
`, fmtd)
|
||||
}
|
||||
|
||||
func TestFormatRowsAggregated(t *testing.T) {
|
||||
store, err := NewStore(fmt.Sprintf(TEST_CONN_STRING_PATTERN, "format-rows"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
rows, err := store.db.Query("SELECT count(1) as total, trade_node from provinces where region = 'France' group by trade_node")
|
||||
assert.NoError(t, err)
|
||||
|
||||
fmtd, err := FormatRows(rows)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `| total | trade_node |
|
||||
| ----- | --------------- |
|
||||
| 25 | Bordeaux |
|
||||
| 24 | Champagne |
|
||||
| 8 | English Channel |
|
||||
| 4 | Genoa |
|
||||
| 5 | Valencia |
|
||||
`, fmtd)
|
||||
}
|
||||
|
||||
func TestFormatRowsInvalidQuery(t *testing.T) {
|
||||
store, err := NewStore(fmt.Sprintf(TEST_CONN_STRING_PATTERN, "format-rows"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = store.db.Query("SELECT count(name), distinct(trade_node) from provinces where region = 'France'")
|
||||
assert.Error(t, err)
|
||||
}
|
Loading…
Reference in new issue