From 512858c17cc5ac879007b0688b2b0b543d7a60d9 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 25 Aug 2021 10:50:06 +0200 Subject: [PATCH 1/2] C++ generated code - add missing or include --- internal/generator/c/cgenerator.go | 3 ++- internal/generator/c/templates/binding-hpp.go | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/generator/c/cgenerator.go b/internal/generator/c/cgenerator.go index a53d332..5eaa1a7 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 3149c6a..45fd811 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" From 7a6c6afe1f71a68e79030a49cd5bd059fc2dbddc Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 25 Aug 2021 10:02:41 +0200 Subject: [PATCH 2/2] Go - add relation backlink support from to-one relation --- internal/generator/binding/backlink.go | 31 + internal/generator/binding/object.go | 1 + internal/generator/generator.go | 8 +- internal/generator/go/ast-reader.go | 58 +- internal/generator/go/gogenerator.go | 37 +- internal/generator/go/templates/binding.go | 90 ++- internal/generator/merge.go | 3 +- internal/generator/model/relation.go | 18 +- internal/generator/model/relcycles.go | 12 +- .../go/cycles/embedding-named.fail.go | 2 +- .../testdata/go/cycles/embedding.fail.go | 2 +- .../testdata/go/relations/1group.go | 2 + .../go/relations/1group.obx.go.expected | 34 +- .../testdata/go/relations/backlink.go | 11 + .../go/relations/backlink.obx.go.expected | 715 ++++++++++++++++++ .../go/relations/byid.obx.go.expected | 20 +- .../go/relations/bypointer.obx.go.expected | 20 +- .../go/relations/byvalue.obx.go.expected | 20 +- .../go/relations/embedded.obx.go.expected | 22 +- .../relations/manybypointer.obx.go.expected | 18 +- .../go/relations/manybyvalue.obx.go.expected | 18 +- .../go/relations/objectbox-model.go.expected | 8 +- .../relations/objectbox-model.json.expected | 96 ++- 23 files changed, 1076 insertions(+), 170 deletions(-) create mode 100644 internal/generator/binding/backlink.go create mode 100644 test/comparison/testdata/go/relations/backlink.go create mode 100644 test/comparison/testdata/go/relations/backlink.obx.go.expected diff --git a/internal/generator/binding/backlink.go b/internal/generator/binding/backlink.go new file mode 100644 index 0000000..9521959 --- /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 4b0fcda..cbde80f 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/generator.go b/internal/generator/generator.go index b8ab247..037f6fd 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 e7dfa80..48dbb42 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..aa54990 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 353a888..781d404 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 c5b5b3a..b261dfb 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 c0c3840..fdf9246 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 8c88524..1b43aee 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 8107ff8..bdaa921 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 17dc6df..ff5f805 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 c269b38..25bbf80 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 bbab907..3f295d1 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 0000000..6bf6873 --- /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 0000000..da15721 --- /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 ebc1e7c..603814d 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 9dfb0e9..15944ee 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 8e7b822..a8ba64f 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 6c947c0..1f552fc 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 5849591..ca64c02 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 87e8b27..7f0e2bf 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 6ec7e91..acbb797 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 290753f..e3bbd7e 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": [],