diff --git a/query/query.go b/query/query.go index 5d1ab98..cf567c0 100644 --- a/query/query.go +++ b/query/query.go @@ -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) diff --git a/query/query_test.go b/query/query_test.go index 94cbcfa..33519a4 100644 --- a/query/query_test.go +++ b/query/query_test.go @@ -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) }