From ee5acc6ded1ffb1e0c946d72faaac65aa1fea1a1 Mon Sep 17 00:00:00 2001 From: Gibheer Date: Wed, 28 Apr 2021 21:05:31 +0200 Subject: [PATCH] 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. --- query/query.go | 46 +++++++++++++++++++++++++++++++++++++++ query/query_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++ query/rows.go | 35 ++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 query/query.go create mode 100644 query/query_test.go create mode 100644 query/rows.go diff --git a/query/query.go b/query/query.go new file mode 100644 index 0000000..21ea483 --- /dev/null +++ b/query/query.go @@ -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, "->")) +} diff --git a/query/query_test.go b/query/query_test.go new file mode 100644 index 0000000..d07c9c4 --- /dev/null +++ b/query/query_test.go @@ -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) + } + } +} diff --git a/query/rows.go b/query/rows.go new file mode 100644 index 0000000..f15f573 --- /dev/null +++ b/query/rows.go @@ -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 +}