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:
parent
1015861af8
commit
ee5acc6ded
|
@ -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, "->"))
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue