add function FieldsToJSON

This function enables the show view to be much less write overhead.
By defining which columns to return and automatically merging the
attributes into the main view, this can be made so much easier.

It doesn't support the recursive view for now, so that is something a
client would need to handle, but for now this should be good enough.

This also fixes a small issue in the update clause handler by moving
the index handler into the handler when a column was found.
If that was not done, the index gets moved to the wrong position and the
resulting query would be wrongly indexed.
This commit is contained in:
Gibheer 2021-05-07 21:03:58 +02:00
parent f96f8ba4ed
commit 88c271667b
2 changed files with 46 additions and 16 deletions

View File

@ -36,6 +36,18 @@ func FieldListToSelect(tableName string, fl types.FieldList, nameMap map[string]
return strings.Join(res, ",")
}
// FieldsToJSON converts a map of field names to columns to build a json output.
// The attributes column of the table is merged into the resulting JSON.
// Fields must contain the name of the resulting field and the column name where the value
// is coming from.
func FieldsToJSON(table string, fields map[string]string) string {
res := []string{}
for key, val := range fields {
res = append(res, fmt.Sprintf("'%s', %s", key, val))
}
return fmt.Sprintf("jsonb_build_object(%s) || attributes", strings.Join(res, ","))
}
// nameToAttrPath takes a dotted string and converts it into a json field path.
func nameToAttrPath(tabName, name string) string {
if name == "" {
@ -67,8 +79,8 @@ func FieldMapToUpdate(fm types.FieldMap, nameMap map[string]string) (string, []i
attrVals := map[string]interface{}{}
i := 0
for key, val := range fm.Fields() {
i++
if name, found := nameMap[key]; found {
i++
setClause = append(setClause, fmt.Sprintf("%s = $%d", name, i))
if val == "" {
args = append(args, nil)

View File

@ -13,18 +13,23 @@ func TestFieldListToSelect(t *testing.T) {
mapping map[string]string
out string
}{
{"foo", types.NewFieldList("name"), map[string]string{"name": "name"}, "name as name"},
{
"foo",
types.NewFieldList("name"),
map[string]string{"name": "name"},
`name as "name"`,
},
{
"foo",
types.NewFieldList("foo", "bar", "baz"),
map[string]string{"foo": "c.foo", "bar": "b.bar"},
"b.bar as bar,foo.attributes->'baz' as baz,c.foo as foo",
`b.bar as "bar",foo.attributes->>'baz' as "baz",c.foo as "foo"`,
},
{
"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",
`p.name as "name",s.subnets as "subnets",vlan.vlan_id as "vlan"`,
},
}
@ -36,14 +41,28 @@ func TestFieldListToSelect(t *testing.T) {
}
}
func TestJSONSelect(t *testing.T) {
tests := []struct {
table string
fields map[string]string
out string
}{}
for _, test := range tests {
out := FieldsToJSON(test.table, test.fields)
if out != test.out {
t.Errorf("expected `%s`, got `%s`", test.out, out)
}
}
}
func TestNameToAttrPath(t *testing.T) {
tests := []struct {
table string
in string
out string
}{
{"zoo", "foo", `zoo.attributes->'foo'`},
{"zoo", "foo.bar", `zoo.attributes->'foo'->'bar'`},
{"zoo", "foo", `zoo.attributes->>'foo'`},
{"zoo", "foo.bar", `zoo.attributes->'foo'->>'bar'`},
{"zoo", "", `zoo.attributes`},
}
@ -57,37 +76,36 @@ func TestNameToAttrPath(t *testing.T) {
func TestFieldMapToUpdate(t *testing.T) {
tests := []struct {
table string
vals types.FieldMap
mapping map[string]string
set string // expected set clause
args []interface{} // expected arguments
}{
{ // check for normal field mapping
"zoo",
types.NewFieldMap(map[string]interface{}{"key": "value"}),
map[string]string{"key": "field"},
"zoo.field = $1",
"field = $1",
[]interface{}{"value"},
},
{ // generate attributes field
"zoo",
types.NewFieldMap(map[string]interface{}{"key2": "value"}),
map[string]string{"key": "field"},
"zoo.attributes->'key2' = $1",
[]interface{}{"value"},
"attributes = jsonb_strip_nulls(attributes || $1::jsonb)",
[]interface{}{`{"key2":"value"}`},
},
{ // mixed mapped and unmapped field
"zoo",
types.NewFieldMap(map[string]interface{}{"key2": "value", "key": "value"}),
map[string]string{"key": "field"},
"zoo.attributes->'key2' = $1,zoo.field = $2",
[]interface{}{"value", "value"},
"field = $1,attributes = jsonb_strip_nulls(attributes || $2::jsonb)",
[]interface{}{"value", `{"key2":"value"}`},
},
}
for _, test := range tests {
set, args := FieldMapToUpdate(test.table, test.vals, test.mapping)
set, args, err := FieldMapToUpdate(test.vals, test.mapping)
if err != nil {
t.Errorf("could not map to update: %s", err)
}
if set != test.set {
t.Errorf("expected set clause `%s`, got `%s`", test.set, set)
}