diff --git a/internal/generator/binding/backlink.go b/internal/generator/binding/backlink.go
new file mode 100644
index 00000000..95219599
--- /dev/null
+++ b/internal/generator/binding/backlink.go
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 ObjectBox Ltd. All rights reserved.
+ * https://objectbox.io
+ *
+ * This file is part of ObjectBox Generator.
+ *
+ * ObjectBox Generator is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * ObjectBox Generator is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with ObjectBox Generator. If not, see .
+ */
+
+package binding
+
+// Backlink describes a relation backlink. Backlinks aren't stored in the model JSON.
+type Backlink struct {
+ SourceEntity string
+ SourceProperty string
+}
+
+// RelatedEntityName gets the relation source entity name
+func (relation *Backlink) RelatedEntityName() string {
+ return relation.SourceEntity
+}
\ No newline at end of file
diff --git a/internal/generator/binding/object.go b/internal/generator/binding/object.go
index 4b0fcda2..cbde80fa 100644
--- a/internal/generator/binding/object.go
+++ b/internal/generator/binding/object.go
@@ -125,6 +125,7 @@ func (object *Object) AddRelation(details map[string]*Annotation) (*model.Standa
return nil, fmt.Errorf("name annotation value must not be empty on relation - it's the relation name in DB")
}
relation.Name = details["name"].Value
+ relation.IsLazyLoaded = details["lazy"] != nil
if details["to"] == nil || len(details["to"].Value) == 0 {
return nil, fmt.Errorf("to annotation value must not be empty on relation %s - specify target entity", relation.Name)
diff --git a/internal/generator/c/cgenerator.go b/internal/generator/c/cgenerator.go
index a53d332d..5eaa1a70 100644
--- a/internal/generator/c/cgenerator.go
+++ b/internal/generator/c/cgenerator.go
@@ -129,7 +129,8 @@ func (gen *CGenerator) generateBindingFile(bindingFile, headerFile string, m *mo
GeneratorVersion int
FileIdentifier string
HeaderFile string
- }{m, generator.VersionId, fileIdentifier, filepath.Base(headerFile)}
+ Optional string
+ }{m, generator.VersionId, fileIdentifier, filepath.Base(headerFile), gen.Optional}
var tpl *template.Template
diff --git a/internal/generator/c/templates/binding-hpp.go b/internal/generator/c/templates/binding-hpp.go
index 3149c6ad..45fd8117 100644
--- a/internal/generator/c/templates/binding-hpp.go
+++ b/internal/generator/c/templates/binding-hpp.go
@@ -31,6 +31,11 @@ var CppBindingTemplateHeader = template.Must(template.New("binding-hpp").Funcs(f
#include
#include
+{{- if eq "std::optional" .Optional}}
+#include
+{{- else if .Optional}}
+#include
+{{end}}
#include "flatbuffers/flatbuffers.h"
#include "objectbox.h"
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index b8ab2474..037f6fd6 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -166,14 +166,14 @@ func createBinding(options Options, storedModel *model.ModelInfo) error {
return fmt.Errorf("model finalization failed: %s", err)
}
- if err = options.CodeGenerator.WriteBindingFiles(filePath, options, storedModel); err != nil {
- return err
- }
-
for _, entity := range storedModel.EntitiesWithMeta() {
entity.CurrentlyPresent = true
}
+ if err = options.CodeGenerator.WriteBindingFiles(filePath, options, storedModel); err != nil {
+ return err
+ }
+
return nil
})
}
diff --git a/internal/generator/go/ast-reader.go b/internal/generator/go/ast-reader.go
index e7dfa802..48dbb420 100644
--- a/internal/generator/go/ast-reader.go
+++ b/internal/generator/go/ast-reader.go
@@ -46,6 +46,7 @@ var supportedEntityAnnotations = map[string]bool{
var supportedPropertyAnnotations = map[string]bool{
"-": true,
+ "backlink": true,
"converter": true,
"date": true,
"date-nano": true,
@@ -122,7 +123,7 @@ type Field struct {
Property *Property // nil if it's an embedded struct
Fields []*Field // inner fields, nil if it's a property
StandaloneRelation *model.StandaloneRelation // to-many relation stored as a standalone relation in the model
- IsLazyLoaded bool // only standalone (to-many) relations currently support lazy loading
+ Backlink *binding.Backlink // non-null if this field describes a relation backlink
Meta *Field // self reference for recursive ".Meta.Fields" access in the template
path string // relative addressing path for embedded structs
@@ -262,7 +263,7 @@ func (entity *Entity) addFields(parent *Field, fields fieldList, fieldPath, pref
log.Printf("%s property %s found in %s", text, property.Name, fieldPath)
}
var propertyError = func(err error, property *Property) error {
- return fmt.Errorf("%s on property %s found in %s", err, property.Name, fieldPath)
+ return fmt.Errorf("property %s.%s: %s", fieldPath, property.Name, err)
}
var children []*Field
@@ -499,24 +500,35 @@ func (field *Field) processType(f field) (fields fieldList, err error) {
if slice, isSlice := baseType.(*types.Slice); isSlice {
var elementType = slice.Elem()
- // it's a many-to-many relation
- if err := property.setRelationAnnotation(typeBaseName(elementType.String()), true); err != nil {
- return nil, err
- }
+ relatedEntity := typeBaseName(elementType.String())
- var relDetails = make(map[string]*binding.Annotation)
- relDetails["name"] = &binding.Annotation{Value: field.Name}
- relDetails["to"] = property.annotations["link"]
- relDetails["uid"] = property.annotations["uid"]
- if rel, err := field.Entity.AddRelation(relDetails); err != nil {
- return nil, err
+ // if it's a backlink (the source may be either a to-one relation or a to-many relation)
+ if property.annotations["backlink"] != nil {
+ if property.annotations["link"] != nil {
+ return nil, fmt.Errorf("cannot combine 'link' and 'backlink' annotations on a single field")
+ }
+
+ field.Backlink = &binding.Backlink{
+ SourceEntity: relatedEntity,
+ SourceProperty: property.annotations["backlink"].Value,
+ }
+ property.IsBasicType = false // override the value set by setBasicType
} else {
- field.StandaloneRelation = rel
- }
+ // it's a many-to-many relation
+ if err := property.setRelationAnnotation(typeBaseName(elementType.String()), true); err != nil {
+ return nil, err
+ }
- if field.Property.annotations["lazy"] != nil {
- // relations only
- field.IsLazyLoaded = true
+ var relDetails = make(map[string]*binding.Annotation)
+ relDetails["name"] = &binding.Annotation{Value: field.Name}
+ relDetails["to"] = property.annotations["link"]
+ relDetails["uid"] = property.annotations["uid"]
+ relDetails["lazy"] = property.annotations["lazy"]
+ if rel, err := field.Entity.AddRelation(relDetails); err != nil {
+ return nil, err
+ } else {
+ field.StandaloneRelation = rel
+ }
}
// fill in the field information
@@ -772,6 +784,16 @@ func (entity *Entity) HasLazyLoadedRelations() bool {
return false
}
+// ToMany called from the template.
+func (field *Field) ToMany() interface{} {
+ if field.StandaloneRelation != nil {
+ return field.StandaloneRelation
+ } else if field.Backlink != nil {
+ return field.Backlink
+ }
+ return nil
+}
+
// HasRelations called from the template.
func (field *Field) HasRelations() bool {
if field.StandaloneRelation != nil || len(field.Property.ModelProperty.RelationTarget) > 0 {
@@ -789,7 +811,7 @@ func (field *Field) HasRelations() bool {
// HasLazyLoadedRelations called from the template.
func (field *Field) HasLazyLoadedRelations() bool {
- if field.StandaloneRelation != nil && field.IsLazyLoaded {
+ if field.StandaloneRelation != nil && field.StandaloneRelation.IsLazyLoaded {
return true
}
diff --git a/internal/generator/go/gogenerator.go b/internal/generator/go/gogenerator.go
index 85496c1f..aa549901 100644
--- a/internal/generator/go/gogenerator.go
+++ b/internal/generator/go/gogenerator.go
@@ -85,10 +85,43 @@ func (goGen *GoGenerator) ParseSource(sourceFile string) (*model.ModelInfo, erro
return goGen.binding.model, nil
}
+// Update backlink specifications (source property name)
+func resolveBacklinks(mergedModel *model.ModelInfo) error {
+ for _, entity := range mergedModel.Entities {
+ if !entity.CurrentlyPresent {
+ continue
+ }
+ for _, field := range entity.Meta.(*Entity).Fields {
+ if field.Backlink == nil || len(field.Backlink.SourceProperty) != 0 {
+ continue
+ }
+ if srcEntity, err := mergedModel.FindEntityByName(field.Backlink.SourceEntity); err != nil {
+ return fmt.Errorf("backlink %v.%v source lookup failed: %v", entity.Name, field.Name, err)
+ } else {
+ for _, prop := range srcEntity.Properties {
+ if prop.RelationTarget == entity.Name {
+ if len(field.Backlink.SourceProperty) == 0 {
+ field.Backlink.SourceProperty = prop.Name
+ } else {
+ return fmt.Errorf("backlink %v.%v matches multiple source properties: %v and %v",
+ entity.Name, field.Name, field.Backlink.SourceProperty, prop.Name)
+ }
+ }
+ }
+ }
+ }
+ }
+ return nil
+}
+
func (goGen *GoGenerator) WriteBindingFiles(sourceFile string, options generator.Options, mergedModel *model.ModelInfo) error {
// NOTE: find a better place for this check - we only want to do it for some languages
// should be called after generator calls storedMode.Finalize()
- if err := mergedModel.CheckRelationCycles(); err != nil {
+ if err := model.CheckRelationCycles(mergedModel.Entities...); err != nil {
+ return err
+ }
+
+ if err := resolveBacklinks(mergedModel); err != nil {
return err
}
@@ -154,7 +187,7 @@ func (goGen *GoGenerator) WriteModelBindingFile(options generator.Options, model
}
if formattedSource, err := format.Source(modelSource); err != nil {
- // we just store error but still writ the file so that we can check it manually
+ // we just store error but still write the file so that we can check it manually
err2 = fmt.Errorf("failed to format generated model file %s: %s", modelFile, err)
} else {
modelSource = formattedSource
diff --git a/internal/generator/go/templates/binding.go b/internal/generator/go/templates/binding.go
index 353a8888..781d4045 100644
--- a/internal/generator/go/templates/binding.go
+++ b/internal/generator/go/templates/binding.go
@@ -47,6 +47,46 @@ var BindingTemplate = template.Must(template.New("binding").Funcs(funcMap).Parse
{{- else}}{{if .GoField.IsPointer}}*{{end}}obj.{{.Path}}{{end}}
{{- end -}}
+{{define "fetch-toMany"}}{{/* used in fetch-related for standalone relations and backlinks*/ -}}
+ // It will "GetManyExisting()" all related {{.ToMany.RelatedEntityName}} objects for each source object
+ // and set sourceObject.{{.Name}} to the slice of related objects, as currently stored in DB.
+ func (box *{{.Entity.Name}}Box) Fetch{{.Name}}(sourceObjects ...*{{.Entity.Name}}) error {
+ var slices = make([]{{.Type}}, len(sourceObjects))
+ err := box.ObjectBox.RunInReadTx(func() error {
+ // collect slices before setting the source objects' fields
+ // this keeps all the sourceObjects untouched in case there's an error during any of the requests
+ for k, object := range sourceObjects {
+ {{if .Entity.ModelEntity.IdProperty.Meta.Converter -}}
+ sourceId, err := {{.Entity.ModelEntity.IdProperty.Meta.Converter}}ToDatabaseValue(object.{{.Entity.ModelEntity.IdProperty.Meta.Path}})
+ if err != nil {
+ return err
+ }
+ {{end -}}
+ {{if .Backlink -}}
+ rIds, err := BoxFor{{.ToMany.RelatedEntityName}}(box.ObjectBox).RelationBackLinkIds({{.Backlink.RelatedEntityName}}_.{{.Backlink.SourceProperty}},
+ {{- else -}}
+ rIds, err := box.RelationIds({{.Entity.Name}}_.{{.Name}},
+ {{- end -}}
+ {{with .Entity.ModelEntity.IdProperty}} {{if .Meta.Converter}}sourceId{{else}}object.{{.Meta.Path}}{{end}}{{end}})
+ if err == nil {
+ slices[k], err = BoxFor{{.ToMany.RelatedEntityName}}(box.ObjectBox).GetManyExisting(rIds...)
+ }
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+
+ if err == nil { // update the field on all objects if we got all slices
+ for k := range sourceObjects {
+ sourceObjects[k].{{.Name}} = slices[k]
+ }
+ }
+ return err
+ }
+{{- end -}}
+
package {{.Binding.Package.Name}}
@@ -165,11 +205,11 @@ func ({{$entityNameCamel}}_EntityInfo) PutRelated(ob *objectbox.ObjectBox, objec
{{- block "put-relations" $entity}}
{{- range $field := .Meta.Fields}}
{{- if $field.StandaloneRelation}}
- {{- if $field.IsLazyLoaded}} if object.(*{{$field.Entity.Name}}).{{$field.Path}} != nil { // lazy-loaded relations without {{$field.Entity.Name}}Box::Fetch{{$field.Name}}() called are nil {{end}}
+ {{- if $field.StandaloneRelation.IsLazyLoaded}} if object.(*{{$field.Entity.Name}}).{{$field.Path}} != nil { // lazy-loaded relations without {{$field.Entity.Name}}Box::Fetch{{$field.Name}}() called are nil {{end}}
if err := BoxFor{{$field.Entity.Name}}(ob).RelationReplace({{.Entity.Name}}_.{{$field.Name}}, id, object, object.(*{{$field.Entity.Name}}).{{$field.Path}}); err != nil {
return err
}
- {{if $field.IsLazyLoaded}} } {{end}}
+ {{if $field.StandaloneRelation.IsLazyLoaded}} } {{end}}
{{- else if $field.Property}}
{{- if and (not $field.Property.IsBasicType) $field.Property.ModelProperty.RelationTarget}}
if rel := {{if not $field.IsPointer}}&{{end}}object.(*{{$field.Entity.Name}}).{{$field.Path}}; rel != nil {
@@ -296,7 +336,7 @@ func ({{$entityNameCamel}}_EntityInfo) Load(ob *objectbox.ObjectBox, bytes []byt
{{- block "load-relations" $entity}}
{{- range $field := .Meta.Fields}}
{{if $field.StandaloneRelation -}}
- {{if not $field.IsLazyLoaded -}}
+ {{if not $field.StandaloneRelation.IsLazyLoaded -}}
var rel{{$field.Name}} {{$field.Type}}
if rIds, err := BoxFor{{$field.Entity.Name}}(ob).RelationIds({{.Entity.Name}}_.{{$field.Name}}, prop{{.Entity.ModelEntity.IdProperty.Name}}); err != nil {
return nil, err
@@ -333,8 +373,10 @@ func ({{$entityNameCamel}}_EntityInfo) Load(ob *objectbox.ObjectBox, bytes []byt
{{- block "fields-initializer" $entity}}
{{- range $field := .Meta.Fields}}
{{$field.Name}}:
- {{- if $field.StandaloneRelation}}
- {{- if $field.IsLazyLoaded}}nil, // use {{$field.Entity.Name}}Box::Fetch{{$field.Name}}() to fetch this lazy-loaded relation
+ {{- if $field.Backlink}}
+ nil, // use {{$field.Entity.Name}}Box::Fetch{{$field.Name}}() to fetch this lazy-loaded relation backlink
+ {{- else if $field.StandaloneRelation}}
+ {{- if $field.StandaloneRelation.IsLazyLoaded}}nil, // use {{$field.Entity.Name}}Box::Fetch{{$field.Name}}() to fetch this lazy-loaded relation
{{- else}}rel{{$field.Name}}
{{- end}}
{{- else if $field.Property}}
@@ -458,41 +500,13 @@ func (box *{{$entity.Name}}Box) GetAll() ([]{{if not $.ByValue}}*{{end}}{{$entit
{{- block "fetch-related" $entity}}
{{- range $field := .Meta.Fields}}
{{if .StandaloneRelation}}
- {{- if .IsLazyLoaded -}}
+ {{- if .StandaloneRelation.IsLazyLoaded -}}
// Fetch{{.Name}} reads target objects for relation {{.Entity.Name}}::{{.Name}}.
- // It will "GetManyExisting()" all related {{.StandaloneRelation.Target.Name}} objects for each source object
- // and set sourceObject.{{.Name}} to the slice of related objects, as currently stored in DB.
- func (box *{{.Entity.Name}}Box) Fetch{{.Name}}(sourceObjects ...*{{.Entity.Name}}) error {
- var slices = make([]{{.Type}}, len(sourceObjects))
- err := box.ObjectBox.RunInReadTx(func() error {
- // collect slices before setting the source objects' fields
- // this keeps all the sourceObjects untouched in case there's an error during any of the requests
- for k, object := range sourceObjects {
- {{if .Entity.ModelEntity.IdProperty.Meta.Converter -}}
- sourceId, err := {{.Entity.ModelEntity.IdProperty.Meta.Converter}}ToDatabaseValue(object.{{.Entity.ModelEntity.IdProperty.Meta.Path}})
- if err != nil {
- return err
- }
- {{end -}}
- rIds, err := box.RelationIds({{.Entity.Name}}_.{{.Name}}, {{with .Entity.ModelEntity.IdProperty}} {{if .Meta.Converter}}sourceId{{else}}object.{{.Meta.Path}}{{end}}{{end}})
- if err == nil {
- slices[k], err = BoxFor{{.StandaloneRelation.Target.Name}}(box.ObjectBox).GetManyExisting(rIds...)
- }
- if err != nil {
- return err
- }
- }
- return nil
- })
-
- if err == nil { // update the field on all objects if we got all slices
- for k := range sourceObjects {
- sourceObjects[k].{{.Name}} = slices[k]
- }
- }
- return err
- }
+ {{template "fetch-toMany" $field}}
{{end}}
+ {{- else if .Backlink}}
+ // Fetch{{.Name}} reads source objects back-linked by relation {{.Backlink.RelatedEntityName}}::{{.Backlink.SourceProperty}}.
+ {{template "fetch-toMany" $field}}
{{- else if not .Property}}{{/* recursively visit fields in embedded structs */}}{{template "fetch-related" $field}}
{{- end}}
{{- end}}{{end}}
diff --git a/internal/generator/merge.go b/internal/generator/merge.go
index c5b5b3a0..b261dfb1 100644
--- a/internal/generator/merge.go
+++ b/internal/generator/merge.go
@@ -321,6 +321,7 @@ func getModelRelation(currentRelation *model.StandaloneRelation, storedEntity *m
func mergeModelRelation(currentRelation *model.StandaloneRelation, storedRelation *model.StandaloneRelation, storedModel *model.ModelInfo) (err error) {
storedRelation.Name = currentRelation.Name
+ storedRelation.IsLazyLoaded = currentRelation.IsLazyLoaded
if currentRelation.Meta != nil {
storedRelation.Meta = currentRelation.Meta.Merge(storedRelation)
@@ -334,7 +335,7 @@ func mergeModelRelation(currentRelation *model.StandaloneRelation, storedRelatio
currentRelation.Id = storedRelation.Id
}
- // find the target entity & read it's ID/UID for the binding code
+ // find the target entity & read its ID/UID for the binding code
if targetEntity, err := storedModel.FindEntityByName(currentRelation.Target.Name); err != nil {
return err
} else if _, _, err = targetEntity.Id.Get(); err != nil {
diff --git a/internal/generator/model/relation.go b/internal/generator/model/relation.go
index c0c3840b..fdf9246b 100644
--- a/internal/generator/model/relation.go
+++ b/internal/generator/model/relation.go
@@ -26,12 +26,13 @@ import (
// StandaloneRelation in a model
type StandaloneRelation struct {
- Id IdUid `json:"id"`
- Name string `json:"name"`
- Target *Entity `json:"-"` // TODO consider changing to TargetName, nothing else seems to be used.
- TargetId IdUid `json:"targetId"`
- UidRequest bool `json:"-"` // used when the user gives an empty uid annotation // TODO test
- Meta StandaloneRelationMeta `json:"-"`
+ Id IdUid `json:"id"`
+ Name string `json:"name"`
+ Target *Entity `json:"-"` // TODO consider changing to TargetName, nothing else seems to be used.
+ TargetId IdUid `json:"targetId"`
+ UidRequest bool `json:"-"` // used when the user gives an empty uid annotation // TODO test
+ Meta StandaloneRelationMeta `json:"-"`
+ IsLazyLoaded bool `json:"-"` // internal, used when checking for relation cycles (to allow the cycle)
entity *Entity
}
@@ -75,3 +76,8 @@ func (relation *StandaloneRelation) SetTarget(entity *Entity) {
relation.Target = entity
relation.TargetId = entity.Id
}
+
+// RelatedEntityName gets the relation target entity name
+func (relation *StandaloneRelation) RelatedEntityName() string {
+ return relation.Target.Name
+}
\ No newline at end of file
diff --git a/internal/generator/model/relcycles.go b/internal/generator/model/relcycles.go
index 8c885244..1b43aee4 100644
--- a/internal/generator/model/relcycles.go
+++ b/internal/generator/model/relcycles.go
@@ -22,11 +22,10 @@ package model
import "fmt"
// CheckRelationCycles finds relations cycles
-func (model *ModelInfo) CheckRelationCycles() error {
+func CheckRelationCycles(entities ...*Entity) error {
// DFS cycle check, storing relation path in the recursion stack
- var recursionStack = make(map[*Entity]bool)
- for _, entity := range model.Entities {
- if err := entity.checkRelationCycles(&recursionStack, entity.Name); err != nil {
+ for _, entity := range entities {
+ if err := entity.checkRelationCycles(&map[*Entity]bool{}, entity.Name); err != nil {
return err
}
}
@@ -39,6 +38,11 @@ func (entity *Entity) checkRelationCycles(recursionStack *map[*Entity]bool, path
// to-many relations
for _, rel := range entity.Relations {
+ // lazy loading breaks the cycle preventing reads, no need to validate it
+ if rel.IsLazyLoaded {
+ continue
+ }
+
if err := checkRelationCycle(recursionStack, path+"."+rel.Name, rel.Target); err != nil {
return err
}
diff --git a/test/comparison/testdata/go/cycles/embedding-named.fail.go b/test/comparison/testdata/go/cycles/embedding-named.fail.go
index 8107ff8c..bdaa9213 100644
--- a/test/comparison/testdata/go/cycles/embedding-named.fail.go
+++ b/test/comparison/testdata/go/cycles/embedding-named.fail.go
@@ -1,6 +1,6 @@
package object
-// ERROR = can't prepare bindings for cycles/embedding-named.fail.go: embedded struct cycle detected: EmbeddingNamedChainA.BPtr.CPtr on property APtr found in EmbeddingNamedChainA.BPtr.CPtr
+// ERROR = can't prepare bindings for cycles/embedding-named.fail.go: property EmbeddingNamedChainA.BPtr.CPtr.APtr: embedded struct cycle detected: EmbeddingNamedChainA.BPtr.CPtr
type EmbeddingNamedChainA struct {
Id uint64
diff --git a/test/comparison/testdata/go/cycles/embedding.fail.go b/test/comparison/testdata/go/cycles/embedding.fail.go
index 17dc6dfb..ff5f805c 100644
--- a/test/comparison/testdata/go/cycles/embedding.fail.go
+++ b/test/comparison/testdata/go/cycles/embedding.fail.go
@@ -1,6 +1,6 @@
package object
-// ERROR = can't prepare bindings for cycles/embedding.fail.go: embedded struct cycle detected: EmbeddingChainA.EmbeddingChainB.EmbeddingChainC on property EmbeddingChainA found in EmbeddingChainA.EmbeddingChainB.EmbeddingChainC
+// ERROR = can't prepare bindings for cycles/embedding.fail.go: property EmbeddingChainA.EmbeddingChainB.EmbeddingChainC.EmbeddingChainA: embedded struct cycle detected: EmbeddingChainA.EmbeddingChainB.EmbeddingChainC
type EmbeddingChainA struct {
Id uint64
diff --git a/test/comparison/testdata/go/relations/1group.go b/test/comparison/testdata/go/relations/1group.go
index c269b38f..25bbf80e 100644
--- a/test/comparison/testdata/go/relations/1group.go
+++ b/test/comparison/testdata/go/relations/1group.go
@@ -3,4 +3,6 @@ package object
type Group struct {
Id uint64
Name string
+
+ TaskRelPtrs []*TaskRelPtr `objectbox:"backlink:Group"` // Try backlink with a source property name.
}
diff --git a/test/comparison/testdata/go/relations/1group.obx.go.expected b/test/comparison/testdata/go/relations/1group.obx.go.expected
index bbab907a..3f295d17 100644
--- a/test/comparison/testdata/go/relations/1group.obx.go.expected
+++ b/test/comparison/testdata/go/relations/1group.obx.go.expected
@@ -97,8 +97,10 @@ func (group_EntityInfo) Load(ob *objectbox.ObjectBox, bytes []byte) (interface{}
var propId = table.GetUint64Slot(4, 0)
return &Group{
- Id: propId,
- Name: fbutils.GetStringSlot(table, 6),
+ Id: propId,
+ Name: fbutils.GetStringSlot(table, 6),
+ TaskRelPtrs: nil, // use GroupBox::FetchTaskRelPtrs() to fetch this lazy-loaded relation backlink,
+
}, nil
}
@@ -208,6 +210,34 @@ func (box *GroupBox) GetAll() ([]*Group, error) {
return objects.([]*Group), nil
}
+// FetchTaskRelPtrs reads source objects back-linked by relation TaskRelPtr::Group.
+// It will "GetManyExisting()" all related TaskRelPtr objects for each source object
+// and set sourceObject.TaskRelPtrs to the slice of related objects, as currently stored in DB.
+func (box *GroupBox) FetchTaskRelPtrs(sourceObjects ...*Group) error {
+ var slices = make([][]*TaskRelPtr, len(sourceObjects))
+ err := box.ObjectBox.RunInReadTx(func() error {
+ // collect slices before setting the source objects' fields
+ // this keeps all the sourceObjects untouched in case there's an error during any of the requests
+ for k, object := range sourceObjects {
+ rIds, err := BoxForTaskRelPtr(box.ObjectBox).RelationBackLinkIds(TaskRelPtr_.Group, object.Id)
+ if err == nil {
+ slices[k], err = BoxForTaskRelPtr(box.ObjectBox).GetManyExisting(rIds...)
+ }
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+
+ if err == nil { // update the field on all objects if we got all slices
+ for k := range sourceObjects {
+ sourceObjects[k].TaskRelPtrs = slices[k]
+ }
+ }
+ return err
+}
+
// Remove deletes a single object
func (box *GroupBox) Remove(object *Group) error {
return box.Box.Remove(object)
diff --git a/test/comparison/testdata/go/relations/backlink.go b/test/comparison/testdata/go/relations/backlink.go
new file mode 100644
index 00000000..6bf6873c
--- /dev/null
+++ b/test/comparison/testdata/go/relations/backlink.go
@@ -0,0 +1,11 @@
+package object
+
+type Teacher struct {
+ Id uint64
+ Students []*Student `objectbox:"backlink"` // Try backlink without a source property name.
+}
+
+type Student struct {
+ Id uint64
+ Teacher *Teacher `objectbox:"link"`
+}
diff --git a/test/comparison/testdata/go/relations/backlink.obx.go.expected b/test/comparison/testdata/go/relations/backlink.obx.go.expected
new file mode 100644
index 00000000..da157219
--- /dev/null
+++ b/test/comparison/testdata/go/relations/backlink.obx.go.expected
@@ -0,0 +1,715 @@
+// Code generated by ObjectBox; DO NOT EDIT.
+// Learn more about defining entities and generating this file - visit https://golang.objectbox.io/entity-annotations
+
+package object
+
+import (
+ "errors"
+ "github.com/google/flatbuffers/go"
+ "github.com/objectbox/objectbox-go/objectbox"
+ "github.com/objectbox/objectbox-go/objectbox/fbutils"
+)
+
+type teacher_EntityInfo struct {
+ objectbox.Entity
+ Uid uint64
+}
+
+var TeacherBinding = teacher_EntityInfo{
+ Entity: objectbox.Entity{
+ Id: 3,
+ },
+ Uid: 1774932891286980153,
+}
+
+// Teacher_ contains type-based Property helpers to facilitate some common operations such as Queries.
+var Teacher_ = struct {
+ Id *objectbox.PropertyUint64
+}{
+ Id: &objectbox.PropertyUint64{
+ BaseProperty: &objectbox.BaseProperty{
+ Id: 1,
+ Entity: &TeacherBinding.Entity,
+ },
+ },
+}
+
+// GeneratorVersion is called by ObjectBox to verify the compatibility of the generator used to generate this code
+func (teacher_EntityInfo) GeneratorVersion() int {
+ return 6
+}
+
+// AddToModel is called by ObjectBox during model build
+func (teacher_EntityInfo) AddToModel(model *objectbox.Model) {
+ model.Entity("Teacher", 3, 1774932891286980153)
+ model.Property("Id", 6, 1, 8274930044578894929)
+ model.PropertyFlags(1)
+ model.EntityLastPropertyId(1, 8274930044578894929)
+}
+
+// GetId is called by ObjectBox during Put operations to check for existing ID on an object
+func (teacher_EntityInfo) GetId(object interface{}) (uint64, error) {
+ return object.(*Teacher).Id, nil
+}
+
+// SetId is called by ObjectBox during Put to update an ID on an object that has just been inserted
+func (teacher_EntityInfo) SetId(object interface{}, id uint64) error {
+ object.(*Teacher).Id = id
+ return nil
+}
+
+// PutRelated is called by ObjectBox to put related entities before the object itself is flattened and put
+func (teacher_EntityInfo) PutRelated(ob *objectbox.ObjectBox, object interface{}, id uint64) error {
+ return nil
+}
+
+// Flatten is called by ObjectBox to transform an object to a FlatBuffer
+func (teacher_EntityInfo) Flatten(object interface{}, fbb *flatbuffers.Builder, id uint64) error {
+
+ // build the FlatBuffers object
+ fbb.StartObject(1)
+ fbutils.SetUint64Slot(fbb, 0, id)
+ return nil
+}
+
+// Load is called by ObjectBox to load an object from a FlatBuffer
+func (teacher_EntityInfo) Load(ob *objectbox.ObjectBox, bytes []byte) (interface{}, error) {
+ if len(bytes) == 0 { // sanity check, should "never" happen
+ return nil, errors.New("can't deserialize an object of type 'Teacher' - no data received")
+ }
+
+ var table = &flatbuffers.Table{
+ Bytes: bytes,
+ Pos: flatbuffers.GetUOffsetT(bytes),
+ }
+
+ var propId = table.GetUint64Slot(4, 0)
+
+ return &Teacher{
+ Id: propId,
+ Students: nil, // use TeacherBox::FetchStudents() to fetch this lazy-loaded relation backlink,
+
+ }, nil
+}
+
+// MakeSlice is called by ObjectBox to construct a new slice to hold the read objects
+func (teacher_EntityInfo) MakeSlice(capacity int) interface{} {
+ return make([]*Teacher, 0, capacity)
+}
+
+// AppendToSlice is called by ObjectBox to fill the slice of the read objects
+func (teacher_EntityInfo) AppendToSlice(slice interface{}, object interface{}) interface{} {
+ if object == nil {
+ return append(slice.([]*Teacher), nil)
+ }
+ return append(slice.([]*Teacher), object.(*Teacher))
+}
+
+// Box provides CRUD access to Teacher objects
+type TeacherBox struct {
+ *objectbox.Box
+}
+
+// BoxForTeacher opens a box of Teacher objects
+func BoxForTeacher(ob *objectbox.ObjectBox) *TeacherBox {
+ return &TeacherBox{
+ Box: ob.InternalBox(3),
+ }
+}
+
+// Put synchronously inserts/updates a single object.
+// In case the Id is not specified, it would be assigned automatically (auto-increment).
+// When inserting, the Teacher.Id property on the passed object will be assigned the new ID as well.
+func (box *TeacherBox) Put(object *Teacher) (uint64, error) {
+ return box.Box.Put(object)
+}
+
+// Insert synchronously inserts a single object. As opposed to Put, Insert will fail if given an ID that already exists.
+// In case the Id is not specified, it would be assigned automatically (auto-increment).
+// When inserting, the Teacher.Id property on the passed object will be assigned the new ID as well.
+func (box *TeacherBox) Insert(object *Teacher) (uint64, error) {
+ return box.Box.Insert(object)
+}
+
+// Update synchronously updates a single object.
+// As opposed to Put, Update will fail if an object with the same ID is not found in the database.
+func (box *TeacherBox) Update(object *Teacher) error {
+ return box.Box.Update(object)
+}
+
+// PutAsync asynchronously inserts/updates a single object.
+// Deprecated: use box.Async().Put() instead
+func (box *TeacherBox) PutAsync(object *Teacher) (uint64, error) {
+ return box.Box.PutAsync(object)
+}
+
+// PutMany inserts multiple objects in single transaction.
+// In case Ids are not set on the objects, they would be assigned automatically (auto-increment).
+//
+// Returns: IDs of the put objects (in the same order).
+// When inserting, the Teacher.Id property on the objects in the slice will be assigned the new IDs as well.
+//
+// Note: In case an error occurs during the transaction, some of the objects may already have the Teacher.Id assigned
+// even though the transaction has been rolled back and the objects are not stored under those IDs.
+//
+// Note: The slice may be empty or even nil; in both cases, an empty IDs slice and no error is returned.
+func (box *TeacherBox) PutMany(objects []*Teacher) ([]uint64, error) {
+ return box.Box.PutMany(objects)
+}
+
+// Get reads a single object.
+//
+// Returns nil (and no error) in case the object with the given ID doesn't exist.
+func (box *TeacherBox) Get(id uint64) (*Teacher, error) {
+ object, err := box.Box.Get(id)
+ if err != nil {
+ return nil, err
+ } else if object == nil {
+ return nil, nil
+ }
+ return object.(*Teacher), nil
+}
+
+// GetMany reads multiple objects at once.
+// If any of the objects doesn't exist, its position in the return slice is nil
+func (box *TeacherBox) GetMany(ids ...uint64) ([]*Teacher, error) {
+ objects, err := box.Box.GetMany(ids...)
+ if err != nil {
+ return nil, err
+ }
+ return objects.([]*Teacher), nil
+}
+
+// GetManyExisting reads multiple objects at once, skipping those that do not exist.
+func (box *TeacherBox) GetManyExisting(ids ...uint64) ([]*Teacher, error) {
+ objects, err := box.Box.GetManyExisting(ids...)
+ if err != nil {
+ return nil, err
+ }
+ return objects.([]*Teacher), nil
+}
+
+// GetAll reads all stored objects
+func (box *TeacherBox) GetAll() ([]*Teacher, error) {
+ objects, err := box.Box.GetAll()
+ if err != nil {
+ return nil, err
+ }
+ return objects.([]*Teacher), nil
+}
+
+// FetchStudents reads source objects back-linked by relation Student::Teacher.
+// It will "GetManyExisting()" all related Student objects for each source object
+// and set sourceObject.Students to the slice of related objects, as currently stored in DB.
+func (box *TeacherBox) FetchStudents(sourceObjects ...*Teacher) error {
+ var slices = make([][]*Student, len(sourceObjects))
+ err := box.ObjectBox.RunInReadTx(func() error {
+ // collect slices before setting the source objects' fields
+ // this keeps all the sourceObjects untouched in case there's an error during any of the requests
+ for k, object := range sourceObjects {
+ rIds, err := BoxForStudent(box.ObjectBox).RelationBackLinkIds(Student_.Teacher, object.Id)
+ if err == nil {
+ slices[k], err = BoxForStudent(box.ObjectBox).GetManyExisting(rIds...)
+ }
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+
+ if err == nil { // update the field on all objects if we got all slices
+ for k := range sourceObjects {
+ sourceObjects[k].Students = slices[k]
+ }
+ }
+ return err
+}
+
+// Remove deletes a single object
+func (box *TeacherBox) Remove(object *Teacher) error {
+ return box.Box.Remove(object)
+}
+
+// RemoveMany deletes multiple objects at once.
+// Returns the number of deleted object or error on failure.
+// Note that this method will not fail if an object is not found (e.g. already removed).
+// In case you need to strictly check whether all of the objects exist before removing them,
+// you can execute multiple box.Contains() and box.Remove() inside a single write transaction.
+func (box *TeacherBox) RemoveMany(objects ...*Teacher) (uint64, error) {
+ var ids = make([]uint64, len(objects))
+ for k, object := range objects {
+ ids[k] = object.Id
+ }
+ return box.Box.RemoveIds(ids...)
+}
+
+// Creates a query with the given conditions. Use the fields of the Teacher_ struct to create conditions.
+// Keep the *TeacherQuery if you intend to execute the query multiple times.
+// Note: this function panics if you try to create illegal queries; e.g. use properties of an alien type.
+// This is typically a programming error. Use QueryOrError instead if you want the explicit error check.
+func (box *TeacherBox) Query(conditions ...objectbox.Condition) *TeacherQuery {
+ return &TeacherQuery{
+ box.Box.Query(conditions...),
+ }
+}
+
+// Creates a query with the given conditions. Use the fields of the Teacher_ struct to create conditions.
+// Keep the *TeacherQuery if you intend to execute the query multiple times.
+func (box *TeacherBox) QueryOrError(conditions ...objectbox.Condition) (*TeacherQuery, error) {
+ if query, err := box.Box.QueryOrError(conditions...); err != nil {
+ return nil, err
+ } else {
+ return &TeacherQuery{query}, nil
+ }
+}
+
+// Async provides access to the default Async Box for asynchronous operations. See TeacherAsyncBox for more information.
+func (box *TeacherBox) Async() *TeacherAsyncBox {
+ return &TeacherAsyncBox{AsyncBox: box.Box.Async()}
+}
+
+// TeacherAsyncBox provides asynchronous operations on Teacher objects.
+//
+// Asynchronous operations are executed on a separate internal thread for better performance.
+//
+// There are two main use cases:
+//
+// 1) "execute & forget:" you gain faster put/remove operations as you don't have to wait for the transaction to finish.
+//
+// 2) Many small transactions: if your write load is typically a lot of individual puts that happen in parallel,
+// this will merge small transactions into bigger ones. This results in a significant gain in overall throughput.
+//
+// In situations with (extremely) high async load, an async method may be throttled (~1ms) or delayed up to 1 second.
+// In the unlikely event that the object could still not be enqueued (full queue), an error will be returned.
+//
+// Note that async methods do not give you hard durability guarantees like the synchronous Box provides.
+// There is a small time window in which the data may not have been committed durably yet.
+type TeacherAsyncBox struct {
+ *objectbox.AsyncBox
+}
+
+// AsyncBoxForTeacher creates a new async box with the given operation timeout in case an async queue is full.
+// The returned struct must be freed explicitly using the Close() method.
+// It's usually preferable to use TeacherBox::Async() which takes care of resource management and doesn't require closing.
+func AsyncBoxForTeacher(ob *objectbox.ObjectBox, timeoutMs uint64) *TeacherAsyncBox {
+ var async, err = objectbox.NewAsyncBox(ob, 3, timeoutMs)
+ if err != nil {
+ panic("Could not create async box for entity ID 3: %s" + err.Error())
+ }
+ return &TeacherAsyncBox{AsyncBox: async}
+}
+
+// Put inserts/updates a single object asynchronously.
+// When inserting a new object, the Id property on the passed object will be assigned the new ID the entity would hold
+// if the insert is ultimately successful. The newly assigned ID may not become valid if the insert fails.
+func (asyncBox *TeacherAsyncBox) Put(object *Teacher) (uint64, error) {
+ return asyncBox.AsyncBox.Put(object)
+}
+
+// Insert a single object asynchronously.
+// The Id property on the passed object will be assigned the new ID the entity would hold if the insert is ultimately
+// successful. The newly assigned ID may not become valid if the insert fails.
+// Fails silently if an object with the same ID already exists (this error is not returned).
+func (asyncBox *TeacherAsyncBox) Insert(object *Teacher) (id uint64, err error) {
+ return asyncBox.AsyncBox.Insert(object)
+}
+
+// Update a single object asynchronously.
+// The object must already exists or the update fails silently (without an error returned).
+func (asyncBox *TeacherAsyncBox) Update(object *Teacher) error {
+ return asyncBox.AsyncBox.Update(object)
+}
+
+// Remove deletes a single object asynchronously.
+func (asyncBox *TeacherAsyncBox) Remove(object *Teacher) error {
+ return asyncBox.AsyncBox.Remove(object)
+}
+
+// Query provides a way to search stored objects
+//
+// For example, you can find all Teacher which Id is either 42 or 47:
+// box.Query(Teacher_.Id.In(42, 47)).Find()
+type TeacherQuery struct {
+ *objectbox.Query
+}
+
+// Find returns all objects matching the query
+func (query *TeacherQuery) Find() ([]*Teacher, error) {
+ objects, err := query.Query.Find()
+ if err != nil {
+ return nil, err
+ }
+ return objects.([]*Teacher), nil
+}
+
+// Offset defines the index of the first object to process (how many objects to skip)
+func (query *TeacherQuery) Offset(offset uint64) *TeacherQuery {
+ query.Query.Offset(offset)
+ return query
+}
+
+// Limit sets the number of elements to process by the query
+func (query *TeacherQuery) Limit(limit uint64) *TeacherQuery {
+ query.Query.Limit(limit)
+ return query
+}
+
+type student_EntityInfo struct {
+ objectbox.Entity
+ Uid uint64
+}
+
+var StudentBinding = student_EntityInfo{
+ Entity: objectbox.Entity{
+ Id: 4,
+ },
+ Uid: 6044372234677422456,
+}
+
+// Student_ contains type-based Property helpers to facilitate some common operations such as Queries.
+var Student_ = struct {
+ Id *objectbox.PropertyUint64
+ Teacher *objectbox.RelationToOne
+}{
+ Id: &objectbox.PropertyUint64{
+ BaseProperty: &objectbox.BaseProperty{
+ Id: 1,
+ Entity: &StudentBinding.Entity,
+ },
+ },
+ Teacher: &objectbox.RelationToOne{
+ Property: &objectbox.BaseProperty{
+ Id: 2,
+ Entity: &StudentBinding.Entity,
+ },
+ Target: &TeacherBinding.Entity,
+ },
+}
+
+// GeneratorVersion is called by ObjectBox to verify the compatibility of the generator used to generate this code
+func (student_EntityInfo) GeneratorVersion() int {
+ return 6
+}
+
+// AddToModel is called by ObjectBox during model build
+func (student_EntityInfo) AddToModel(model *objectbox.Model) {
+ model.Entity("Student", 4, 6044372234677422456)
+ model.Property("Id", 6, 1, 1543572285742637646)
+ model.PropertyFlags(1)
+ model.Property("Teacher", 11, 2, 2661732831099943416)
+ model.PropertyFlags(520)
+ model.PropertyRelation("Teacher", 1, 8325060299420976708)
+ model.EntityLastPropertyId(2, 2661732831099943416)
+}
+
+// GetId is called by ObjectBox during Put operations to check for existing ID on an object
+func (student_EntityInfo) GetId(object interface{}) (uint64, error) {
+ return object.(*Student).Id, nil
+}
+
+// SetId is called by ObjectBox during Put to update an ID on an object that has just been inserted
+func (student_EntityInfo) SetId(object interface{}, id uint64) error {
+ object.(*Student).Id = id
+ return nil
+}
+
+// PutRelated is called by ObjectBox to put related entities before the object itself is flattened and put
+func (student_EntityInfo) PutRelated(ob *objectbox.ObjectBox, object interface{}, id uint64) error {
+ if rel := object.(*Student).Teacher; rel != nil {
+ if rId, err := TeacherBinding.GetId(rel); err != nil {
+ return err
+ } else if rId == 0 {
+ // NOTE Put/PutAsync() has a side-effect of setting the rel.ID
+ if _, err := BoxForTeacher(ob).Put(rel); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// Flatten is called by ObjectBox to transform an object to a FlatBuffer
+func (student_EntityInfo) Flatten(object interface{}, fbb *flatbuffers.Builder, id uint64) error {
+ obj := object.(*Student)
+
+ var rIdTeacher uint64
+ if rel := obj.Teacher; rel != nil {
+ if rId, err := TeacherBinding.GetId(rel); err != nil {
+ return err
+ } else {
+ rIdTeacher = rId
+ }
+ }
+
+ // build the FlatBuffers object
+ fbb.StartObject(2)
+ fbutils.SetUint64Slot(fbb, 0, id)
+ if obj.Teacher != nil {
+ fbutils.SetUint64Slot(fbb, 1, rIdTeacher)
+ }
+ return nil
+}
+
+// Load is called by ObjectBox to load an object from a FlatBuffer
+func (student_EntityInfo) Load(ob *objectbox.ObjectBox, bytes []byte) (interface{}, error) {
+ if len(bytes) == 0 { // sanity check, should "never" happen
+ return nil, errors.New("can't deserialize an object of type 'Student' - no data received")
+ }
+
+ var table = &flatbuffers.Table{
+ Bytes: bytes,
+ Pos: flatbuffers.GetUOffsetT(bytes),
+ }
+
+ var propId = table.GetUint64Slot(4, 0)
+
+ var relTeacher *Teacher
+ if rId := fbutils.GetUint64PtrSlot(table, 6); rId != nil && *rId > 0 {
+ if rObject, err := BoxForTeacher(ob).Get(*rId); err != nil {
+ return nil, err
+ } else {
+ relTeacher = rObject
+ }
+ }
+
+ return &Student{
+ Id: propId,
+ Teacher: relTeacher,
+ }, nil
+}
+
+// MakeSlice is called by ObjectBox to construct a new slice to hold the read objects
+func (student_EntityInfo) MakeSlice(capacity int) interface{} {
+ return make([]*Student, 0, capacity)
+}
+
+// AppendToSlice is called by ObjectBox to fill the slice of the read objects
+func (student_EntityInfo) AppendToSlice(slice interface{}, object interface{}) interface{} {
+ if object == nil {
+ return append(slice.([]*Student), nil)
+ }
+ return append(slice.([]*Student), object.(*Student))
+}
+
+// Box provides CRUD access to Student objects
+type StudentBox struct {
+ *objectbox.Box
+}
+
+// BoxForStudent opens a box of Student objects
+func BoxForStudent(ob *objectbox.ObjectBox) *StudentBox {
+ return &StudentBox{
+ Box: ob.InternalBox(4),
+ }
+}
+
+// Put synchronously inserts/updates a single object.
+// In case the Id is not specified, it would be assigned automatically (auto-increment).
+// When inserting, the Student.Id property on the passed object will be assigned the new ID as well.
+func (box *StudentBox) Put(object *Student) (uint64, error) {
+ return box.Box.Put(object)
+}
+
+// Insert synchronously inserts a single object. As opposed to Put, Insert will fail if given an ID that already exists.
+// In case the Id is not specified, it would be assigned automatically (auto-increment).
+// When inserting, the Student.Id property on the passed object will be assigned the new ID as well.
+func (box *StudentBox) Insert(object *Student) (uint64, error) {
+ return box.Box.Insert(object)
+}
+
+// Update synchronously updates a single object.
+// As opposed to Put, Update will fail if an object with the same ID is not found in the database.
+func (box *StudentBox) Update(object *Student) error {
+ return box.Box.Update(object)
+}
+
+// PutAsync asynchronously inserts/updates a single object.
+// Deprecated: use box.Async().Put() instead
+func (box *StudentBox) PutAsync(object *Student) (uint64, error) {
+ return box.Box.PutAsync(object)
+}
+
+// PutMany inserts multiple objects in single transaction.
+// In case Ids are not set on the objects, they would be assigned automatically (auto-increment).
+//
+// Returns: IDs of the put objects (in the same order).
+// When inserting, the Student.Id property on the objects in the slice will be assigned the new IDs as well.
+//
+// Note: In case an error occurs during the transaction, some of the objects may already have the Student.Id assigned
+// even though the transaction has been rolled back and the objects are not stored under those IDs.
+//
+// Note: The slice may be empty or even nil; in both cases, an empty IDs slice and no error is returned.
+func (box *StudentBox) PutMany(objects []*Student) ([]uint64, error) {
+ return box.Box.PutMany(objects)
+}
+
+// Get reads a single object.
+//
+// Returns nil (and no error) in case the object with the given ID doesn't exist.
+func (box *StudentBox) Get(id uint64) (*Student, error) {
+ object, err := box.Box.Get(id)
+ if err != nil {
+ return nil, err
+ } else if object == nil {
+ return nil, nil
+ }
+ return object.(*Student), nil
+}
+
+// GetMany reads multiple objects at once.
+// If any of the objects doesn't exist, its position in the return slice is nil
+func (box *StudentBox) GetMany(ids ...uint64) ([]*Student, error) {
+ objects, err := box.Box.GetMany(ids...)
+ if err != nil {
+ return nil, err
+ }
+ return objects.([]*Student), nil
+}
+
+// GetManyExisting reads multiple objects at once, skipping those that do not exist.
+func (box *StudentBox) GetManyExisting(ids ...uint64) ([]*Student, error) {
+ objects, err := box.Box.GetManyExisting(ids...)
+ if err != nil {
+ return nil, err
+ }
+ return objects.([]*Student), nil
+}
+
+// GetAll reads all stored objects
+func (box *StudentBox) GetAll() ([]*Student, error) {
+ objects, err := box.Box.GetAll()
+ if err != nil {
+ return nil, err
+ }
+ return objects.([]*Student), nil
+}
+
+// Remove deletes a single object
+func (box *StudentBox) Remove(object *Student) error {
+ return box.Box.Remove(object)
+}
+
+// RemoveMany deletes multiple objects at once.
+// Returns the number of deleted object or error on failure.
+// Note that this method will not fail if an object is not found (e.g. already removed).
+// In case you need to strictly check whether all of the objects exist before removing them,
+// you can execute multiple box.Contains() and box.Remove() inside a single write transaction.
+func (box *StudentBox) RemoveMany(objects ...*Student) (uint64, error) {
+ var ids = make([]uint64, len(objects))
+ for k, object := range objects {
+ ids[k] = object.Id
+ }
+ return box.Box.RemoveIds(ids...)
+}
+
+// Creates a query with the given conditions. Use the fields of the Student_ struct to create conditions.
+// Keep the *StudentQuery if you intend to execute the query multiple times.
+// Note: this function panics if you try to create illegal queries; e.g. use properties of an alien type.
+// This is typically a programming error. Use QueryOrError instead if you want the explicit error check.
+func (box *StudentBox) Query(conditions ...objectbox.Condition) *StudentQuery {
+ return &StudentQuery{
+ box.Box.Query(conditions...),
+ }
+}
+
+// Creates a query with the given conditions. Use the fields of the Student_ struct to create conditions.
+// Keep the *StudentQuery if you intend to execute the query multiple times.
+func (box *StudentBox) QueryOrError(conditions ...objectbox.Condition) (*StudentQuery, error) {
+ if query, err := box.Box.QueryOrError(conditions...); err != nil {
+ return nil, err
+ } else {
+ return &StudentQuery{query}, nil
+ }
+}
+
+// Async provides access to the default Async Box for asynchronous operations. See StudentAsyncBox for more information.
+func (box *StudentBox) Async() *StudentAsyncBox {
+ return &StudentAsyncBox{AsyncBox: box.Box.Async()}
+}
+
+// StudentAsyncBox provides asynchronous operations on Student objects.
+//
+// Asynchronous operations are executed on a separate internal thread for better performance.
+//
+// There are two main use cases:
+//
+// 1) "execute & forget:" you gain faster put/remove operations as you don't have to wait for the transaction to finish.
+//
+// 2) Many small transactions: if your write load is typically a lot of individual puts that happen in parallel,
+// this will merge small transactions into bigger ones. This results in a significant gain in overall throughput.
+//
+// In situations with (extremely) high async load, an async method may be throttled (~1ms) or delayed up to 1 second.
+// In the unlikely event that the object could still not be enqueued (full queue), an error will be returned.
+//
+// Note that async methods do not give you hard durability guarantees like the synchronous Box provides.
+// There is a small time window in which the data may not have been committed durably yet.
+type StudentAsyncBox struct {
+ *objectbox.AsyncBox
+}
+
+// AsyncBoxForStudent creates a new async box with the given operation timeout in case an async queue is full.
+// The returned struct must be freed explicitly using the Close() method.
+// It's usually preferable to use StudentBox::Async() which takes care of resource management and doesn't require closing.
+func AsyncBoxForStudent(ob *objectbox.ObjectBox, timeoutMs uint64) *StudentAsyncBox {
+ var async, err = objectbox.NewAsyncBox(ob, 4, timeoutMs)
+ if err != nil {
+ panic("Could not create async box for entity ID 4: %s" + err.Error())
+ }
+ return &StudentAsyncBox{AsyncBox: async}
+}
+
+// Put inserts/updates a single object asynchronously.
+// When inserting a new object, the Id property on the passed object will be assigned the new ID the entity would hold
+// if the insert is ultimately successful. The newly assigned ID may not become valid if the insert fails.
+func (asyncBox *StudentAsyncBox) Put(object *Student) (uint64, error) {
+ return asyncBox.AsyncBox.Put(object)
+}
+
+// Insert a single object asynchronously.
+// The Id property on the passed object will be assigned the new ID the entity would hold if the insert is ultimately
+// successful. The newly assigned ID may not become valid if the insert fails.
+// Fails silently if an object with the same ID already exists (this error is not returned).
+func (asyncBox *StudentAsyncBox) Insert(object *Student) (id uint64, err error) {
+ return asyncBox.AsyncBox.Insert(object)
+}
+
+// Update a single object asynchronously.
+// The object must already exists or the update fails silently (without an error returned).
+func (asyncBox *StudentAsyncBox) Update(object *Student) error {
+ return asyncBox.AsyncBox.Update(object)
+}
+
+// Remove deletes a single object asynchronously.
+func (asyncBox *StudentAsyncBox) Remove(object *Student) error {
+ return asyncBox.AsyncBox.Remove(object)
+}
+
+// Query provides a way to search stored objects
+//
+// For example, you can find all Student which Id is either 42 or 47:
+// box.Query(Student_.Id.In(42, 47)).Find()
+type StudentQuery struct {
+ *objectbox.Query
+}
+
+// Find returns all objects matching the query
+func (query *StudentQuery) Find() ([]*Student, error) {
+ objects, err := query.Query.Find()
+ if err != nil {
+ return nil, err
+ }
+ return objects.([]*Student), nil
+}
+
+// Offset defines the index of the first object to process (how many objects to skip)
+func (query *StudentQuery) Offset(offset uint64) *StudentQuery {
+ query.Query.Offset(offset)
+ return query
+}
+
+// Limit sets the number of elements to process by the query
+func (query *StudentQuery) Limit(limit uint64) *StudentQuery {
+ query.Query.Limit(limit)
+ return query
+}
diff --git a/test/comparison/testdata/go/relations/byid.obx.go.expected b/test/comparison/testdata/go/relations/byid.obx.go.expected
index ebc1e7c7..603814db 100644
--- a/test/comparison/testdata/go/relations/byid.obx.go.expected
+++ b/test/comparison/testdata/go/relations/byid.obx.go.expected
@@ -17,9 +17,9 @@ type taskRelId_EntityInfo struct {
var TaskRelIdBinding = taskRelId_EntityInfo{
Entity: objectbox.Entity{
- Id: 3,
+ Id: 5,
},
- Uid: 1774932891286980153,
+ Uid: 7837839688282259259,
}
// TaskRelId_ contains type-based Property helpers to facilitate some common operations such as Queries.
@@ -49,13 +49,13 @@ func (taskRelId_EntityInfo) GeneratorVersion() int {
// AddToModel is called by ObjectBox during model build
func (taskRelId_EntityInfo) AddToModel(model *objectbox.Model) {
- model.Entity("TaskRelId", 3, 1774932891286980153)
- model.Property("Id", 6, 1, 6044372234677422456)
+ model.Entity("TaskRelId", 5, 7837839688282259259)
+ model.Property("Id", 6, 1, 2518412263346885298)
model.PropertyFlags(1)
- model.Property("Group", 11, 2, 8274930044578894929)
+ model.Property("Group", 11, 2, 5617773211005988520)
model.PropertyFlags(520)
- model.PropertyRelation("Group", 1, 1543572285742637646)
- model.EntityLastPropertyId(2, 8274930044578894929)
+ model.PropertyRelation("Group", 2, 2339563716805116249)
+ model.EntityLastPropertyId(2, 5617773211005988520)
}
// GetId is called by ObjectBox during Put operations to check for existing ID on an object
@@ -127,7 +127,7 @@ type TaskRelIdBox struct {
// BoxForTaskRelId opens a box of TaskRelId objects
func BoxForTaskRelId(ob *objectbox.ObjectBox) *TaskRelIdBox {
return &TaskRelIdBox{
- Box: ob.InternalBox(3),
+ Box: ob.InternalBox(5),
}
}
@@ -279,9 +279,9 @@ type TaskRelIdAsyncBox struct {
// The returned struct must be freed explicitly using the Close() method.
// It's usually preferable to use TaskRelIdBox::Async() which takes care of resource management and doesn't require closing.
func AsyncBoxForTaskRelId(ob *objectbox.ObjectBox, timeoutMs uint64) *TaskRelIdAsyncBox {
- var async, err = objectbox.NewAsyncBox(ob, 3, timeoutMs)
+ var async, err = objectbox.NewAsyncBox(ob, 5, timeoutMs)
if err != nil {
- panic("Could not create async box for entity ID 3: %s" + err.Error())
+ panic("Could not create async box for entity ID 5: %s" + err.Error())
}
return &TaskRelIdAsyncBox{AsyncBox: async}
}
diff --git a/test/comparison/testdata/go/relations/bypointer.obx.go.expected b/test/comparison/testdata/go/relations/bypointer.obx.go.expected
index 9dfb0e99..15944ee3 100644
--- a/test/comparison/testdata/go/relations/bypointer.obx.go.expected
+++ b/test/comparison/testdata/go/relations/bypointer.obx.go.expected
@@ -17,9 +17,9 @@ type taskRelPtr_EntityInfo struct {
var TaskRelPtrBinding = taskRelPtr_EntityInfo{
Entity: objectbox.Entity{
- Id: 4,
+ Id: 6,
},
- Uid: 2661732831099943416,
+ Uid: 7144924247938981575,
}
// TaskRelPtr_ contains type-based Property helpers to facilitate some common operations such as Queries.
@@ -49,13 +49,13 @@ func (taskRelPtr_EntityInfo) GeneratorVersion() int {
// AddToModel is called by ObjectBox during model build
func (taskRelPtr_EntityInfo) AddToModel(model *objectbox.Model) {
- model.Entity("TaskRelPtr", 4, 2661732831099943416)
- model.Property("Id", 6, 1, 8325060299420976708)
+ model.Entity("TaskRelPtr", 6, 7144924247938981575)
+ model.Property("Id", 6, 1, 161231572858529631)
model.PropertyFlags(1)
- model.Property("Group", 11, 2, 7837839688282259259)
+ model.Property("Group", 11, 2, 7259475919510918339)
model.PropertyFlags(520)
- model.PropertyRelation("Group", 2, 2518412263346885298)
- model.EntityLastPropertyId(2, 7837839688282259259)
+ model.PropertyRelation("Group", 3, 7373105480197164748)
+ model.EntityLastPropertyId(2, 7259475919510918339)
}
// GetId is called by ObjectBox during Put operations to check for existing ID on an object
@@ -155,7 +155,7 @@ type TaskRelPtrBox struct {
// BoxForTaskRelPtr opens a box of TaskRelPtr objects
func BoxForTaskRelPtr(ob *objectbox.ObjectBox) *TaskRelPtrBox {
return &TaskRelPtrBox{
- Box: ob.InternalBox(4),
+ Box: ob.InternalBox(6),
}
}
@@ -307,9 +307,9 @@ type TaskRelPtrAsyncBox struct {
// The returned struct must be freed explicitly using the Close() method.
// It's usually preferable to use TaskRelPtrBox::Async() which takes care of resource management and doesn't require closing.
func AsyncBoxForTaskRelPtr(ob *objectbox.ObjectBox, timeoutMs uint64) *TaskRelPtrAsyncBox {
- var async, err = objectbox.NewAsyncBox(ob, 4, timeoutMs)
+ var async, err = objectbox.NewAsyncBox(ob, 6, timeoutMs)
if err != nil {
- panic("Could not create async box for entity ID 4: %s" + err.Error())
+ panic("Could not create async box for entity ID 6: %s" + err.Error())
}
return &TaskRelPtrAsyncBox{AsyncBox: async}
}
diff --git a/test/comparison/testdata/go/relations/byvalue.obx.go.expected b/test/comparison/testdata/go/relations/byvalue.obx.go.expected
index 8e7b822b..a8ba64fd 100644
--- a/test/comparison/testdata/go/relations/byvalue.obx.go.expected
+++ b/test/comparison/testdata/go/relations/byvalue.obx.go.expected
@@ -17,9 +17,9 @@ type taskRelValue_EntityInfo struct {
var TaskRelValueBinding = taskRelValue_EntityInfo{
Entity: objectbox.Entity{
- Id: 5,
+ Id: 7,
},
- Uid: 5617773211005988520,
+ Uid: 3287288577352441706,
}
// TaskRelValue_ contains type-based Property helpers to facilitate some common operations such as Queries.
@@ -49,13 +49,13 @@ func (taskRelValue_EntityInfo) GeneratorVersion() int {
// AddToModel is called by ObjectBox during model build
func (taskRelValue_EntityInfo) AddToModel(model *objectbox.Model) {
- model.Entity("TaskRelValue", 5, 5617773211005988520)
- model.Property("Id", 6, 1, 2339563716805116249)
+ model.Entity("TaskRelValue", 7, 3287288577352441706)
+ model.Property("Id", 6, 1, 3930927879439176946)
model.PropertyFlags(1)
- model.Property("Group", 11, 2, 7144924247938981575)
+ model.Property("Group", 11, 2, 4706154865122290029)
model.PropertyFlags(520)
- model.PropertyRelation("Group", 3, 161231572858529631)
- model.EntityLastPropertyId(2, 7144924247938981575)
+ model.PropertyRelation("Group", 4, 2217592893536642650)
+ model.EntityLastPropertyId(2, 4706154865122290029)
}
// GetId is called by ObjectBox during Put operations to check for existing ID on an object
@@ -157,7 +157,7 @@ type TaskRelValueBox struct {
// BoxForTaskRelValue opens a box of TaskRelValue objects
func BoxForTaskRelValue(ob *objectbox.ObjectBox) *TaskRelValueBox {
return &TaskRelValueBox{
- Box: ob.InternalBox(5),
+ Box: ob.InternalBox(7),
}
}
@@ -309,9 +309,9 @@ type TaskRelValueAsyncBox struct {
// The returned struct must be freed explicitly using the Close() method.
// It's usually preferable to use TaskRelValueBox::Async() which takes care of resource management and doesn't require closing.
func AsyncBoxForTaskRelValue(ob *objectbox.ObjectBox, timeoutMs uint64) *TaskRelValueAsyncBox {
- var async, err = objectbox.NewAsyncBox(ob, 5, timeoutMs)
+ var async, err = objectbox.NewAsyncBox(ob, 7, timeoutMs)
if err != nil {
- panic("Could not create async box for entity ID 5: %s" + err.Error())
+ panic("Could not create async box for entity ID 7: %s" + err.Error())
}
return &TaskRelValueAsyncBox{AsyncBox: async}
}
diff --git a/test/comparison/testdata/go/relations/embedded.obx.go.expected b/test/comparison/testdata/go/relations/embedded.obx.go.expected
index 6c947c05..1f552fc8 100644
--- a/test/comparison/testdata/go/relations/embedded.obx.go.expected
+++ b/test/comparison/testdata/go/relations/embedded.obx.go.expected
@@ -17,9 +17,9 @@ type taskRelEmbedded_EntityInfo struct {
var TaskRelEmbeddedBinding = taskRelEmbedded_EntityInfo{
Entity: objectbox.Entity{
- Id: 6,
+ Id: 8,
},
- Uid: 7259475919510918339,
+ Uid: 1929546706668609706,
}
// TaskRelEmbedded_ contains type-based Property helpers to facilitate some common operations such as Queries.
@@ -55,14 +55,14 @@ func (taskRelEmbedded_EntityInfo) GeneratorVersion() int {
// AddToModel is called by ObjectBox during model build
func (taskRelEmbedded_EntityInfo) AddToModel(model *objectbox.Model) {
- model.Entity("TaskRelEmbedded", 6, 7259475919510918339)
- model.Property("Id", 6, 1, 7373105480197164748)
+ model.Entity("TaskRelEmbedded", 8, 1929546706668609706)
+ model.Property("Id", 6, 1, 6392442863481646880)
model.PropertyFlags(1)
- model.Property("Group", 11, 2, 3287288577352441706)
+ model.Property("Group", 11, 2, 3706853784096366226)
model.PropertyFlags(520)
- model.PropertyRelation("Group", 4, 3930927879439176946)
- model.EntityLastPropertyId(2, 3287288577352441706)
- model.Relation(1, 4706154865122290029, GroupBinding.Id, GroupBinding.Uid)
+ model.PropertyRelation("Group", 5, 2627038740284806767)
+ model.EntityLastPropertyId(2, 3706853784096366226)
+ model.Relation(1, 6303220950515014660, GroupBinding.Id, GroupBinding.Uid)
}
// GetId is called by ObjectBox during Put operations to check for existing ID on an object
@@ -178,7 +178,7 @@ type TaskRelEmbeddedBox struct {
// BoxForTaskRelEmbedded opens a box of TaskRelEmbedded objects
func BoxForTaskRelEmbedded(ob *objectbox.ObjectBox) *TaskRelEmbeddedBox {
return &TaskRelEmbeddedBox{
- Box: ob.InternalBox(6),
+ Box: ob.InternalBox(8),
}
}
@@ -330,9 +330,9 @@ type TaskRelEmbeddedAsyncBox struct {
// The returned struct must be freed explicitly using the Close() method.
// It's usually preferable to use TaskRelEmbeddedBox::Async() which takes care of resource management and doesn't require closing.
func AsyncBoxForTaskRelEmbedded(ob *objectbox.ObjectBox, timeoutMs uint64) *TaskRelEmbeddedAsyncBox {
- var async, err = objectbox.NewAsyncBox(ob, 6, timeoutMs)
+ var async, err = objectbox.NewAsyncBox(ob, 8, timeoutMs)
if err != nil {
- panic("Could not create async box for entity ID 6: %s" + err.Error())
+ panic("Could not create async box for entity ID 8: %s" + err.Error())
}
return &TaskRelEmbeddedAsyncBox{AsyncBox: async}
}
diff --git a/test/comparison/testdata/go/relations/manybypointer.obx.go.expected b/test/comparison/testdata/go/relations/manybypointer.obx.go.expected
index 5849591b..ca64c027 100644
--- a/test/comparison/testdata/go/relations/manybypointer.obx.go.expected
+++ b/test/comparison/testdata/go/relations/manybypointer.obx.go.expected
@@ -17,9 +17,9 @@ type taskRelManyPtr_EntityInfo struct {
var TaskRelManyPtrBinding = taskRelManyPtr_EntityInfo{
Entity: objectbox.Entity{
- Id: 7,
+ Id: 9,
},
- Uid: 2217592893536642650,
+ Uid: 4035568504096476779,
}
// TaskRelManyPtr_ contains type-based Property helpers to facilitate some common operations such as Queries.
@@ -47,11 +47,11 @@ func (taskRelManyPtr_EntityInfo) GeneratorVersion() int {
// AddToModel is called by ObjectBox during model build
func (taskRelManyPtr_EntityInfo) AddToModel(model *objectbox.Model) {
- model.Entity("TaskRelManyPtr", 7, 2217592893536642650)
- model.Property("Id", 6, 1, 1929546706668609706)
+ model.Entity("TaskRelManyPtr", 9, 4035568504096476779)
+ model.Property("Id", 6, 1, 959367522974354090)
model.PropertyFlags(1)
- model.EntityLastPropertyId(1, 1929546706668609706)
- model.Relation(2, 6392442863481646880, GroupBinding.Id, GroupBinding.Uid)
+ model.EntityLastPropertyId(1, 959367522974354090)
+ model.Relation(2, 2914295034816259174, GroupBinding.Id, GroupBinding.Uid)
}
// GetId is called by ObjectBox during Put operations to check for existing ID on an object
@@ -125,7 +125,7 @@ type TaskRelManyPtrBox struct {
// BoxForTaskRelManyPtr opens a box of TaskRelManyPtr objects
func BoxForTaskRelManyPtr(ob *objectbox.ObjectBox) *TaskRelManyPtrBox {
return &TaskRelManyPtrBox{
- Box: ob.InternalBox(7),
+ Box: ob.InternalBox(9),
}
}
@@ -305,9 +305,9 @@ type TaskRelManyPtrAsyncBox struct {
// The returned struct must be freed explicitly using the Close() method.
// It's usually preferable to use TaskRelManyPtrBox::Async() which takes care of resource management and doesn't require closing.
func AsyncBoxForTaskRelManyPtr(ob *objectbox.ObjectBox, timeoutMs uint64) *TaskRelManyPtrAsyncBox {
- var async, err = objectbox.NewAsyncBox(ob, 7, timeoutMs)
+ var async, err = objectbox.NewAsyncBox(ob, 9, timeoutMs)
if err != nil {
- panic("Could not create async box for entity ID 7: %s" + err.Error())
+ panic("Could not create async box for entity ID 9: %s" + err.Error())
}
return &TaskRelManyPtrAsyncBox{AsyncBox: async}
}
diff --git a/test/comparison/testdata/go/relations/manybyvalue.obx.go.expected b/test/comparison/testdata/go/relations/manybyvalue.obx.go.expected
index 87e8b274..7f0e2bfd 100644
--- a/test/comparison/testdata/go/relations/manybyvalue.obx.go.expected
+++ b/test/comparison/testdata/go/relations/manybyvalue.obx.go.expected
@@ -17,9 +17,9 @@ type taskRelManyValue_EntityInfo struct {
var TaskRelManyValueBinding = taskRelManyValue_EntityInfo{
Entity: objectbox.Entity{
- Id: 8,
+ Id: 10,
},
- Uid: 3706853784096366226,
+ Uid: 1395437218309923052,
}
// TaskRelManyValue_ contains type-based Property helpers to facilitate some common operations such as Queries.
@@ -47,11 +47,11 @@ func (taskRelManyValue_EntityInfo) GeneratorVersion() int {
// AddToModel is called by ObjectBox during model build
func (taskRelManyValue_EntityInfo) AddToModel(model *objectbox.Model) {
- model.Entity("TaskRelManyValue", 8, 3706853784096366226)
- model.Property("Id", 6, 1, 2627038740284806767)
+ model.Entity("TaskRelManyValue", 10, 1395437218309923052)
+ model.Property("Id", 6, 1, 6745438398739480977)
model.PropertyFlags(1)
- model.EntityLastPropertyId(1, 2627038740284806767)
- model.Relation(3, 6303220950515014660, GroupByValBinding.Id, GroupByValBinding.Uid)
+ model.EntityLastPropertyId(1, 6745438398739480977)
+ model.Relation(3, 2897681629866238117, GroupByValBinding.Id, GroupByValBinding.Uid)
}
// GetId is called by ObjectBox during Put operations to check for existing ID on an object
@@ -132,7 +132,7 @@ type TaskRelManyValueBox struct {
// BoxForTaskRelManyValue opens a box of TaskRelManyValue objects
func BoxForTaskRelManyValue(ob *objectbox.ObjectBox) *TaskRelManyValueBox {
return &TaskRelManyValueBox{
- Box: ob.InternalBox(8),
+ Box: ob.InternalBox(10),
}
}
@@ -284,9 +284,9 @@ type TaskRelManyValueAsyncBox struct {
// The returned struct must be freed explicitly using the Close() method.
// It's usually preferable to use TaskRelManyValueBox::Async() which takes care of resource management and doesn't require closing.
func AsyncBoxForTaskRelManyValue(ob *objectbox.ObjectBox, timeoutMs uint64) *TaskRelManyValueAsyncBox {
- var async, err = objectbox.NewAsyncBox(ob, 8, timeoutMs)
+ var async, err = objectbox.NewAsyncBox(ob, 10, timeoutMs)
if err != nil {
- panic("Could not create async box for entity ID 8: %s" + err.Error())
+ panic("Could not create async box for entity ID 10: %s" + err.Error())
}
return &TaskRelManyValueAsyncBox{AsyncBox: async}
}
diff --git a/test/comparison/testdata/go/relations/objectbox-model.go.expected b/test/comparison/testdata/go/relations/objectbox-model.go.expected
index 6ec7e918..acbb797d 100644
--- a/test/comparison/testdata/go/relations/objectbox-model.go.expected
+++ b/test/comparison/testdata/go/relations/objectbox-model.go.expected
@@ -14,15 +14,17 @@ func ObjectBoxModel() *objectbox.Model {
model.RegisterBinding(GroupBinding)
model.RegisterBinding(GroupByValBinding)
+ model.RegisterBinding(TeacherBinding)
+ model.RegisterBinding(StudentBinding)
model.RegisterBinding(TaskRelIdBinding)
model.RegisterBinding(TaskRelPtrBinding)
model.RegisterBinding(TaskRelValueBinding)
model.RegisterBinding(TaskRelEmbeddedBinding)
model.RegisterBinding(TaskRelManyPtrBinding)
model.RegisterBinding(TaskRelManyValueBinding)
- model.LastEntityId(8, 3706853784096366226)
- model.LastIndexId(4, 3930927879439176946)
- model.LastRelationId(3, 6303220950515014660)
+ model.LastEntityId(10, 1395437218309923052)
+ model.LastIndexId(5, 2627038740284806767)
+ model.LastRelationId(3, 2897681629866238117)
return model
}
diff --git a/test/comparison/testdata/go/relations/objectbox-model.json.expected b/test/comparison/testdata/go/relations/objectbox-model.json.expected
index 290753f1..e3bbd7ee 100644
--- a/test/comparison/testdata/go/relations/objectbox-model.json.expected
+++ b/test/comparison/testdata/go/relations/objectbox-model.json.expected
@@ -41,19 +41,53 @@
},
{
"id": "3:1774932891286980153",
- "lastPropertyId": "2:8274930044578894929",
+ "lastPropertyId": "1:8274930044578894929",
+ "name": "Teacher",
+ "properties": [
+ {
+ "id": "1:8274930044578894929",
+ "name": "Id",
+ "type": 6,
+ "flags": 1
+ }
+ ]
+ },
+ {
+ "id": "4:6044372234677422456",
+ "lastPropertyId": "2:2661732831099943416",
+ "name": "Student",
+ "properties": [
+ {
+ "id": "1:1543572285742637646",
+ "name": "Id",
+ "type": 6,
+ "flags": 1
+ },
+ {
+ "id": "2:2661732831099943416",
+ "name": "Teacher",
+ "indexId": "1:8325060299420976708",
+ "type": 11,
+ "flags": 520,
+ "relationTarget": "Teacher"
+ }
+ ]
+ },
+ {
+ "id": "5:7837839688282259259",
+ "lastPropertyId": "2:5617773211005988520",
"name": "TaskRelId",
"properties": [
{
- "id": "1:6044372234677422456",
+ "id": "1:2518412263346885298",
"name": "Id",
"type": 6,
"flags": 1
},
{
- "id": "2:8274930044578894929",
+ "id": "2:5617773211005988520",
"name": "Group",
- "indexId": "1:1543572285742637646",
+ "indexId": "2:2339563716805116249",
"type": 11,
"flags": 520,
"relationTarget": "Group"
@@ -61,20 +95,20 @@
]
},
{
- "id": "4:2661732831099943416",
- "lastPropertyId": "2:7837839688282259259",
+ "id": "6:7144924247938981575",
+ "lastPropertyId": "2:7259475919510918339",
"name": "TaskRelPtr",
"properties": [
{
- "id": "1:8325060299420976708",
+ "id": "1:161231572858529631",
"name": "Id",
"type": 6,
"flags": 1
},
{
- "id": "2:7837839688282259259",
+ "id": "2:7259475919510918339",
"name": "Group",
- "indexId": "2:2518412263346885298",
+ "indexId": "3:7373105480197164748",
"type": 11,
"flags": 520,
"relationTarget": "Group"
@@ -82,20 +116,20 @@
]
},
{
- "id": "5:5617773211005988520",
- "lastPropertyId": "2:7144924247938981575",
+ "id": "7:3287288577352441706",
+ "lastPropertyId": "2:4706154865122290029",
"name": "TaskRelValue",
"properties": [
{
- "id": "1:2339563716805116249",
+ "id": "1:3930927879439176946",
"name": "Id",
"type": 6,
"flags": 1
},
{
- "id": "2:7144924247938981575",
+ "id": "2:4706154865122290029",
"name": "Group",
- "indexId": "3:161231572858529631",
+ "indexId": "4:2217592893536642650",
"type": 11,
"flags": 520,
"relationTarget": "Group"
@@ -103,20 +137,20 @@
]
},
{
- "id": "6:7259475919510918339",
- "lastPropertyId": "2:3287288577352441706",
+ "id": "8:1929546706668609706",
+ "lastPropertyId": "2:3706853784096366226",
"name": "TaskRelEmbedded",
"properties": [
{
- "id": "1:7373105480197164748",
+ "id": "1:6392442863481646880",
"name": "Id",
"type": 6,
"flags": 1
},
{
- "id": "2:3287288577352441706",
+ "id": "2:3706853784096366226",
"name": "Group",
- "indexId": "4:3930927879439176946",
+ "indexId": "5:2627038740284806767",
"type": 11,
"flags": 520,
"relationTarget": "Group"
@@ -124,19 +158,19 @@
],
"relations": [
{
- "id": "1:4706154865122290029",
+ "id": "1:6303220950515014660",
"name": "Groups",
"targetId": "1:8717895732742165505"
}
]
},
{
- "id": "7:2217592893536642650",
- "lastPropertyId": "1:1929546706668609706",
+ "id": "9:4035568504096476779",
+ "lastPropertyId": "1:959367522974354090",
"name": "TaskRelManyPtr",
"properties": [
{
- "id": "1:1929546706668609706",
+ "id": "1:959367522974354090",
"name": "Id",
"type": 6,
"flags": 1
@@ -144,19 +178,19 @@
],
"relations": [
{
- "id": "2:6392442863481646880",
+ "id": "2:2914295034816259174",
"name": "Groups",
"targetId": "1:8717895732742165505"
}
]
},
{
- "id": "8:3706853784096366226",
- "lastPropertyId": "1:2627038740284806767",
+ "id": "10:1395437218309923052",
+ "lastPropertyId": "1:6745438398739480977",
"name": "TaskRelManyValue",
"properties": [
{
- "id": "1:2627038740284806767",
+ "id": "1:6745438398739480977",
"name": "Id",
"type": 6,
"flags": 1
@@ -164,16 +198,16 @@
],
"relations": [
{
- "id": "3:6303220950515014660",
+ "id": "3:2897681629866238117",
"name": "Groups",
"targetId": "2:501233450539197794"
}
]
}
],
- "lastEntityId": "8:3706853784096366226",
- "lastIndexId": "4:3930927879439176946",
- "lastRelationId": "3:6303220950515014660",
+ "lastEntityId": "10:1395437218309923052",
+ "lastIndexId": "5:2627038740284806767",
+ "lastRelationId": "3:2897681629866238117",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [],