add query interface

This is a small library to build queries and put the result into the
world.
Currently it supports building the select clause and converting rows
into a list of maps, so that it can be returned as a list.
This commit is contained in:
Gibheer 2021-04-28 21:05:31 +02:00
parent 1015861af8
commit ee5acc6ded
3 changed files with 133 additions and 0 deletions

46
query/query.go Normal file
View File

@ -0,0 +1,46 @@
/*
Package query provides functions useful for building database queries.
It contains functions to build select clauses or where clauses together
with the necessary parameter keys.
*/
package query
import (
"fmt"
"strings"
"dim/types"
)
// FieldListToSelect converts the fieldlist to a select clause.
//
// It takes a fieldField and tries to find a matching name in the nameMap and
// uses the provided name.
// Any field that is not found will be converted to an attributes selector, so
// that extra attributes can be selected dynamically.
// Each field will be selected with the requested name, so be careful to
// avoid collisions.
func FieldListToSelect(fl types.FieldList, nameMap map[string]string) string {
res := []string{}
for _, name := range fl.Fields() {
if field, found := nameMap[name]; found {
res = append(res, fmt.Sprintf(`%s as %s`, field, name))
} else {
res = append(res, fmt.Sprintf(`%s as %s`, nameToAttrPath(name), name))
}
}
return strings.Join(res, ",")
}
// nameToAttrPath takes a dotted string and converts it into a json field path.
func nameToAttrPath(name string) string {
if name == "" {
return "attributes"
}
parts := strings.Split(name, ".")
for i, part := range parts {
parts[i] = fmt.Sprintf(`'%s'`, part)
}
return fmt.Sprintf("attributes->%s", strings.Join(parts, "->"))
}

52
query/query_test.go Normal file
View File

@ -0,0 +1,52 @@
package query
import (
"dim/types"
"testing"
)
func TestFieldListToSelect(t *testing.T) {
tests := []struct {
names types.FieldList
mapping map[string]string
out string
}{
{types.NewFieldList("name"), map[string]string{"name": "name"}, "name as name"},
{
types.NewFieldList("foo", "bar", "baz"),
map[string]string{"foo": "c.foo", "bar": "b.bar"},
"b.bar as bar,attributes->'baz' as baz,c.foo as foo",
},
{
types.NewFieldList("subnets", "vlan", "name"),
map[string]string{"subnets": "s.subnets", "vlan": "vlan.vlan_id", "name": "p.name"},
"p.name as name,s.subnets as subnets,vlan.vlan_id as vlan",
},
}
for _, test := range tests {
out := FieldListToSelect(test.names, test.mapping)
if out != test.out {
t.Errorf("expected `%s`, got `%s`", test.out, out)
}
}
}
func TestNameToAttrPath(t *testing.T) {
tests := []struct {
in string
out string
}{
{"foo", `attributes->'foo'`},
{"foo.bar", `attributes->'foo'->'bar'`},
{"", `attributes`},
}
for _, test := range tests {
out := nameToAttrPath(test.in)
if test.out != out {
t.Errorf("expected `%s`, got `%s`", test.out, out)
}
}
}

35
query/rows.go Normal file
View File

@ -0,0 +1,35 @@
package query
import (
"database/sql"
"fmt"
)
// RowsToMap converts a query result to a list of maps.
func RowsToMap(rows *sql.Rows) ([]map[string]interface{}, error) {
empty := []map[string]interface{}{}
res := []map[string]interface{}{}
cols, err := rows.Columns()
if err != nil {
return empty, fmt.Errorf("could not get columns: %v", err)
}
for rows.Next() {
if rows.Err() != nil {
return empty, fmt.Errorf("could not iterate rows: %v", err)
}
raw := make([]interface{}, len(cols))
for i, _ := range raw {
raw[i] = new(interface{})
}
if err := rows.Scan(raw...); err != nil {
return empty, fmt.Errorf("could not scan row: %v", err)
}
row := map[string]interface{}{}
for i, col := range cols {
row[col] = raw[i]
}
res = append(res, row)
}
return res, nil
}