Skip to content
This repository was archived by the owner on Sep 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## UNRELEASED

- feat: add readfrom json tag to support reverse edges
[#49](https://github.com/hypermodeinc/modusDB/pull/49)

## 2025-01-02 - Version 0.1.0

Baseline for the changelog.
Expand Down
2 changes: 1 addition & 1 deletion api.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func Query[T any](db *DB, queryParams QueryParams, ns ...uint64) ([]uint64, []T,
return nil, nil, err
}

return executeQuery[T](ctx, n, queryParams, false)
return executeQuery[T](ctx, n, queryParams, true)
}

func Delete[T any, R UniqueField](db *DB, uniqueField R, ns ...uint64) (uint64, T, error) {
Expand Down
16 changes: 12 additions & 4 deletions api_dql.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ const (
objQuery = `
{
obj(func: %s) {
uid
gid: uid
expand(_all_) {
uid
gid: uid
expand(_all_)
dgraph.type
}
Expand All @@ -36,9 +36,9 @@ const (
objsQuery = `
{
objs(func: type("%s")%s) @filter(%s) {
uid
gid: uid
expand(_all_) {
uid
gid: uid
expand(_all_)
dgraph.type
}
Expand All @@ -48,6 +48,14 @@ const (
}
`

reverseEdgeQuery = `
%s: ~%s {
gid: uid
expand(_all_)
dgraph.type
}
`

funcUid = `uid(%d)`
funcEq = `eq(%s, %s)`
funcSimilarTo = `similar_to(%s, %d, "[%s]")`
Expand Down
37 changes: 28 additions & 9 deletions api_mutate_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"context"
"fmt"
"reflect"
"strings"

"github.com/dgraph-io/dgo/v240/protos/api"
"github.com/dgraph-io/dgraph/v24/dql"
Expand All @@ -39,18 +40,34 @@ func generateCreateDqlMutationsAndSchema[T any](ctx context.Context, n *Namespac
nquads := make([]*api.NQuad, 0)
uniqueConstraintFound := false
for jsonName, value := range jsonTagToValue {
var val *api.Value
var valType pb.Posting_ValType

reflectValueType := reflect.TypeOf(value)
var nquad *api.NQuad

if jsonToReverseEdgeTags[jsonName] != "" {
if reflectValueType.Kind() != reflect.Slice || reflectValueType.Elem().Kind() != reflect.Struct {
return fmt.Errorf("reverse edge %s should be a slice of structs", jsonName)
}
reverseEdge := jsonToReverseEdgeTags[jsonName]
typeName := strings.Split(reverseEdge, ".")[0]
u := &pb.SchemaUpdate{
Predicate: addNamespace(n.id, reverseEdge),
ValueType: pb.Posting_UID,
Directive: pb.SchemaUpdate_REVERSE,
}
sch.Preds = append(sch.Preds, u)
sch.Types = append(sch.Types, &pb.TypeUpdate{
TypeName: addNamespace(n.id, typeName),
Fields: []*pb.SchemaUpdate{u},
})
continue
}
if jsonName == "gid" {
uniqueConstraintFound = true
continue
}
var val *api.Value
var valType pb.Posting_ValType

reflectValueType := reflect.TypeOf(value)
var nquad *api.NQuad

if reflectValueType.Kind() == reflect.Struct {
value = reflect.ValueOf(value).Interface()
Expand Down Expand Up @@ -87,16 +104,18 @@ func generateCreateDqlMutationsAndSchema[T any](ctx context.Context, n *Namespac
Predicate: getPredicateName(t.Name(), jsonName),
}

u := &pb.SchemaUpdate{
Predicate: addNamespace(n.id, getPredicateName(t.Name(), jsonName)),
ValueType: valType,
}

if valType == pb.Posting_UID {
nquad.ObjectId = fmt.Sprint(value)
u.Directive = pb.SchemaUpdate_REVERSE
} else {
nquad.ObjectValue = val
}

u := &pb.SchemaUpdate{
Predicate: addNamespace(n.id, getPredicateName(t.Name(), jsonName)),
ValueType: valType,
}
if jsonToDbTags[jsonName] != nil {
constraint := jsonToDbTags[jsonName].constraint
if constraint == "vector" && valType != pb.Posting_VFLOAT {
Expand Down
20 changes: 4 additions & 16 deletions api_query_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,7 @@ func executeGetWithObject[T any, R UniqueField](ctx context.Context, n *Namespac
readFromQuery := ""
if withReverse {
for jsonTag, reverseEdgeTag := range jsonToReverseEdgeTags {
readFromQuery += fmt.Sprintf(`
%s: ~%s {
uid
expand(_all_)
dgraph.type
}
`, getPredicateName(t.Name(), jsonTag), reverseEdgeTag)
readFromQuery += fmt.Sprintf(reverseEdgeQuery, getPredicateName(t.Name(), jsonTag), reverseEdgeTag)
}
}

Expand Down Expand Up @@ -106,7 +100,7 @@ func executeGetWithObject[T any, R UniqueField](ctx context.Context, n *Namespac

// Map the dynamic struct to the final type T
finalObject := reflect.New(t).Interface()
gid, err = mapDynamicToFinal(result.Obj[0], finalObject)
gid, err = mapDynamicToFinal(result.Obj[0], finalObject, false)
if err != nil {
return 0, obj, err
}
Expand Down Expand Up @@ -152,13 +146,7 @@ func executeQuery[T any](ctx context.Context, n *Namespace, queryParams QueryPar
readFromQuery := ""
if withReverse {
for jsonTag, reverseEdgeTag := range jsonToReverseEdgeTags {
readFromQuery += fmt.Sprintf(`
%s: ~%s {
uid
expand(_all_)
dgraph.type
}
`, getPredicateName(t.Name(), jsonTag), reverseEdgeTag)
readFromQuery += fmt.Sprintf(reverseEdgeQuery, getPredicateName(t.Name(), jsonTag), reverseEdgeTag)
}
}

Expand Down Expand Up @@ -197,7 +185,7 @@ func executeQuery[T any](ctx context.Context, n *Namespace, queryParams QueryPar
var objs []T
for _, obj := range result.Objs {
finalObject := reflect.New(t).Interface()
gid, err := mapDynamicToFinal(obj, finalObject)
gid, err := mapDynamicToFinal(obj, finalObject, false)
if err != nil {
return nil, nil, err
}
Expand Down
54 changes: 41 additions & 13 deletions api_reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func createDynamicStruct(t reflect.Type, fieldToJsonTags map[string]string, dept
field, _ := t.FieldByName(fieldName)
if fieldName != "Gid" {
if field.Type.Kind() == reflect.Struct {
if depth <= 2 {
if depth <= 1 {
nestedFieldToJsonTags, _, _, _ := getFieldTags(field.Type)
nestedType := createDynamicStruct(field.Type, nestedFieldToJsonTags, depth+1)
fields = append(fields, reflect.StructField{
Expand All @@ -100,6 +100,15 @@ func createDynamicStruct(t reflect.Type, fieldToJsonTags map[string]string, dept
Type: reflect.PointerTo(nestedType),
Tag: reflect.StructTag(fmt.Sprintf(`json:"%s.%s"`, t.Name(), jsonName)),
})
} else if field.Type.Kind() == reflect.Slice &&
field.Type.Elem().Kind() == reflect.Struct {
nestedFieldToJsonTags, _, _, _ := getFieldTags(field.Type.Elem())
nestedType := createDynamicStruct(field.Type.Elem(), nestedFieldToJsonTags, depth+1)
fields = append(fields, reflect.StructField{
Name: field.Name,
Type: reflect.SliceOf(nestedType),
Tag: reflect.StructTag(fmt.Sprintf(`json:"%s.%s"`, t.Name(), jsonName)),
})
} else {
fields = append(fields, reflect.StructField{
Name: field.Name,
Expand All @@ -111,9 +120,9 @@ func createDynamicStruct(t reflect.Type, fieldToJsonTags map[string]string, dept
}
}
fields = append(fields, reflect.StructField{
Name: "Uid",
Name: "Gid",
Type: reflect.TypeOf(""),
Tag: reflect.StructTag(`json:"uid"`),
Tag: reflect.StructTag(`json:"gid"`),
}, reflect.StructField{
Name: "DgraphType",
Type: reflect.TypeOf([]string{}),
Expand All @@ -122,7 +131,7 @@ func createDynamicStruct(t reflect.Type, fieldToJsonTags map[string]string, dept
return reflect.StructOf(fields)
}

func mapDynamicToFinal(dynamic any, final any) (uint64, error) {
func mapDynamicToFinal(dynamic any, final any, isNested bool) (uint64, error) {
vFinal := reflect.ValueOf(final).Elem()
vDynamic := reflect.ValueOf(dynamic).Elem()

Expand All @@ -135,35 +144,54 @@ func mapDynamicToFinal(dynamic any, final any) (uint64, error) {
dynamicValue := vDynamic.Field(i)

var finalField reflect.Value
if dynamicField.Name == "Uid" {
if dynamicField.Name == "Gid" {
finalField = vFinal.FieldByName("Gid")
gidStr := dynamicValue.String()
gid, _ = strconv.ParseUint(gidStr, 0, 64)
} else if dynamicField.Name == "DgraphType" {
fieldArr := dynamicValue.Interface().([]string)
if len(fieldArr) == 0 {
return 0, ErrNoObjFound
fieldArrInterface := dynamicValue.Interface()
fieldArr, ok := fieldArrInterface.([]string)
if ok {
if len(fieldArr) == 0 {
if !isNested {
return 0, ErrNoObjFound
} else {
continue
}
}
} else {
return 0, fmt.Errorf("DgraphType field should be an array of strings")
}
} else {
finalField = vFinal.FieldByName(dynamicField.Name)
}
if dynamicFieldType.Kind() == reflect.Struct {
_, err := mapDynamicToFinal(dynamicValue.Addr().Interface(), finalField.Addr().Interface())
_, err := mapDynamicToFinal(dynamicValue.Addr().Interface(), finalField.Addr().Interface(), true)
if err != nil {
return 0, err
}
} else if dynamicFieldType.Kind() == reflect.Ptr &&
dynamicFieldType.Elem().Kind() == reflect.Struct {
// if field is a pointer, find if the underlying is a struct
_, err := mapDynamicToFinal(dynamicValue.Interface(), finalField.Interface())
_, err := mapDynamicToFinal(dynamicValue.Interface(), finalField.Interface(), true)
if err != nil {
return 0, err
}

} else if dynamicFieldType.Kind() == reflect.Slice &&
dynamicFieldType.Elem().Kind() == reflect.Struct {
for j := 0; j < dynamicValue.Len(); j++ {
sliceElem := dynamicValue.Index(j).Addr().Interface()
finalSliceElem := reflect.New(finalField.Type().Elem()).Elem()
_, err := mapDynamicToFinal(sliceElem, finalSliceElem.Addr().Interface(), true)
if err != nil {
return 0, err
}
finalField.Set(reflect.Append(finalField, finalSliceElem))
}
} else {
if finalField.IsValid() && finalField.CanSet() {
// if field name is uid, convert it to uint64
if dynamicField.Name == "Uid" {
// if field name is gid, convert it to uint64
if dynamicField.Name == "Gid" {
finalField.SetUint(gid)
} else {
finalField.Set(dynamicValue)
Expand Down
Loading
Loading