├── go.mod ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── go.yml ├── .gitignore ├── go.sum ├── test ├── performance │ ├── README.md │ ├── perf │ │ ├── objectbox-model.go │ │ ├── entity.go │ │ └── objectbox-model.json │ ├── perf_test.go │ ├── main.go │ └── perf.go ├── model │ ├── iot │ │ ├── objectbox-model.go │ │ ├── model.go │ │ ├── objectbox-model.json │ │ └── helper.go │ ├── byvalue.go │ ├── objectbox-model.go │ ├── timeseries.go │ ├── types.go │ ├── entity.go │ └── testenv.go ├── build │ └── build.go ├── embedding_test.go ├── byvalue_test.go ├── tx_test.go ├── microbench_test.go ├── stringid_test.go ├── async_test.go ├── version_test.go ├── converters_test.go ├── sync_server_test.go ├── concurrency_test.go ├── nil_test.go ├── bench_test.go └── assert │ └── assert.go ├── examples ├── tasks │ ├── internal │ │ └── model │ │ │ ├── objectbox-model.go │ │ │ ├── task.go │ │ │ └── objectbox-model.json │ ├── README.md │ └── main.go └── tutorial │ ├── README.md │ └── setup.sh ├── objectbox ├── internals_test.go ├── fbb.go ├── doc.go ├── entity.go ├── datavisitorc.go ├── fbutils │ ├── utils.go │ ├── setters.go │ └── utils_test.go ├── c-callbacks-c.go ├── datavisitor.go ├── version.go ├── sync.go ├── converters.go ├── builder.go ├── relation.go ├── condition.go ├── asyncbox.go ├── c-callbacks.go └── objectbox.go ├── .gitlab └── merge_request_templates │ └── Default.md ├── .gitlab-ci.yml ├── cmd └── objectbox-gogen │ └── objectbox-gogen.go ├── install.sh ├── install.ps1 └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/objectbox/objectbox-go 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/google/flatbuffers v23.5.26+incompatible 7 | github.com/objectbox/objectbox-generator/v4 v4.0.0 8 | ) 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Question 4 | url: https://stackoverflow.com/questions/tagged/objectbox 5 | about: Ask how to do something, or why it isn't working on Stack Overflow. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binary libs 2 | *.so 3 | *.dylib 4 | *.dll 5 | 6 | # IDE (e.g. CLion) 7 | .idea/ 8 | 9 | # DB files 10 | data.mdb 11 | lock.mdb 12 | 13 | build-artifacts 14 | 15 | # created by install scripts 16 | objectboxlib 17 | download -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg= 2 | github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 3 | github.com/objectbox/objectbox-generator/v4 v4.0.0 h1:7V7t7mkGfZ0fSNhaOuOQWKNfPnq8Q7mC+Uzo9ciq5To= 4 | github.com/objectbox/objectbox-generator/v4 v4.0.0/go.mod h1:paUROSAShse/S8vIhpCyg6leDlZR/C7zOusTeK5YOEY= 5 | -------------------------------------------------------------------------------- /test/performance/README.md: -------------------------------------------------------------------------------- 1 | Performance tests 2 | ================= 3 | 4 | To get good numbers, close all programs before running, build & run outside of IDE: 5 | 6 | ```bash 7 | cd /tmp 8 | go build github.com/objectbox/objectbox-go/test/performance/ 9 | ./performance 10 | ``` 11 | 12 | You can specify some parameters, see `./performance -h`: 13 | ``` 14 | Usage of ./performance: 15 | -count int 16 | number of objects (default 1000000) 17 | -db string 18 | database directory (default "db") 19 | -runs int 20 | number of times the tests should be executed (default 30) 21 | ``` 22 | -------------------------------------------------------------------------------- /test/performance/perf/objectbox-model.go: -------------------------------------------------------------------------------- 1 | // Code generated by ObjectBox; DO NOT EDIT. 2 | 3 | package perf 4 | 5 | import ( 6 | "github.com/objectbox/objectbox-go/objectbox" 7 | ) 8 | 9 | // ObjectBoxModel declares and builds the model from all the entities in the package. 10 | // It is usually used when setting-up ObjectBox as an argument to the Builder.Model() function. 11 | func ObjectBoxModel() *objectbox.Model { 12 | model := objectbox.NewModel() 13 | model.GeneratorVersion(6) 14 | 15 | model.RegisterBinding(EntityBinding) 16 | model.LastEntityId(1, 1737161401460991620) 17 | 18 | return model 19 | } 20 | -------------------------------------------------------------------------------- /examples/tasks/internal/model/objectbox-model.go: -------------------------------------------------------------------------------- 1 | // Code generated by ObjectBox; DO NOT EDIT. 2 | 3 | package model 4 | 5 | import ( 6 | "github.com/objectbox/objectbox-go/objectbox" 7 | ) 8 | 9 | // ObjectBoxModel declares and builds the model from all the entities in the package. 10 | // It is usually used when setting-up ObjectBox as an argument to the Builder.Model() function. 11 | func ObjectBoxModel() *objectbox.Model { 12 | model := objectbox.NewModel() 13 | model.GeneratorVersion(6) 14 | 15 | model.RegisterBinding(TaskBinding) 16 | model.LastEntityId(1, 6645479796472661392) 17 | 18 | return model 19 | } 20 | -------------------------------------------------------------------------------- /objectbox/internals_test.go: -------------------------------------------------------------------------------- 1 | package objectbox 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | ) 7 | 8 | func TestLargeArraySupport(t *testing.T) { 9 | t.Log(internalLibVersion()) 10 | 11 | if runtime.GOARCH == `386` || runtime.GOARCH == `arm` { 12 | if supportsResultArray { 13 | t.Errorf("Expected large array support to be disabled on a 32-bit system (%s) but its enabled "+ 14 | "in the ObjectBox core library", runtime.GOARCH) 15 | } 16 | } else if !supportsResultArray { 17 | t.Errorf("Expected large array support to be enabled on a 64-bit system (%s) but its disabled "+ 18 | "in the ObjectBox core library", runtime.GOARCH) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/model/iot/objectbox-model.go: -------------------------------------------------------------------------------- 1 | // Code generated by ObjectBox; DO NOT EDIT. 2 | 3 | package iot 4 | 5 | import ( 6 | "github.com/objectbox/objectbox-go/objectbox" 7 | ) 8 | 9 | // ObjectBoxModel declares and builds the model from all the entities in the package. 10 | // It is usually used when setting-up ObjectBox as an argument to the Builder.Model() function. 11 | func ObjectBoxModel() *objectbox.Model { 12 | model := objectbox.NewModel() 13 | model.GeneratorVersion(6) 14 | 15 | model.RegisterBinding(EventBinding) 16 | model.RegisterBinding(ReadingBinding) 17 | model.LastEntityId(2, 5284076134434938613) 18 | model.LastIndexId(2, 2642563953244304959) 19 | 20 | return model 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | matrix: 18 | os: [macos-latest, ubuntu-22.04, ubuntu-latest] 19 | go: ["1.18", "1.19", "1.20", "1.21", "1.22", "1.23", "1.24"] 20 | fail-fast: false 21 | runs-on: "${{ matrix.os }}" 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Set up Go "${{ matrix.go }}" 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: "${{ matrix.go }}" 28 | - run: ./build/ci.sh 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | :rotating_light: First, please check: 11 | - existing issues (including closed ones), 12 | - Docs https://golang.objectbox.io/ 13 | - FAQ page https://golang.objectbox.io/faq 14 | 15 | Start with a clear and concise description of what problem you are trying to solve. 16 | Often there is already a solution! 17 | 18 | #### Desired solution 19 | Describe what you would like ObjectBox to do/what you want would happen. 20 | 21 | #### Alternatives 22 | A description of any alternative solutions or features you've considered. 23 | 24 | #### Additional context 25 | Add any other context (e.g. platform or language) about the feature request here. 26 | -------------------------------------------------------------------------------- /objectbox/fbb.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | import ( 20 | "github.com/google/flatbuffers/go" 21 | "sync" 22 | ) 23 | 24 | var fbbPool = sync.Pool{ 25 | New: func() interface{} { 26 | return flatbuffers.NewBuilder(256) 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /test/model/byvalue.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | //go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen -byValue 20 | 21 | // EntityByValue model 22 | type EntityByValue struct { 23 | Id uint64 24 | Text string 25 | } 26 | -------------------------------------------------------------------------------- /test/performance/perf/entity.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package perf 18 | 19 | //go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen 20 | 21 | // Entity model 22 | type Entity struct { 23 | ID uint64 24 | Int32 int32 25 | Int64 int64 26 | String string 27 | Float64 float64 28 | } 29 | -------------------------------------------------------------------------------- /test/model/objectbox-model.go: -------------------------------------------------------------------------------- 1 | // Code generated by ObjectBox; DO NOT EDIT. 2 | 3 | package model 4 | 5 | import ( 6 | "github.com/objectbox/objectbox-go/objectbox" 7 | ) 8 | 9 | // ObjectBoxModel declares and builds the model from all the entities in the package. 10 | // It is usually used when setting-up ObjectBox as an argument to the Builder.Model() function. 11 | func ObjectBoxModel() *objectbox.Model { 12 | model := objectbox.NewModel() 13 | model.GeneratorVersion(6) 14 | 15 | model.RegisterBinding(EntityBinding) 16 | model.RegisterBinding(TestStringIdEntityBinding) 17 | model.RegisterBinding(EntityByValueBinding) 18 | model.RegisterBinding(TestEntityInlineBinding) 19 | model.RegisterBinding(TestEntityRelatedBinding) 20 | model.RegisterBinding(TSDateBinding) 21 | model.RegisterBinding(TSDateNanoBinding) 22 | model.RegisterBinding(TestEntitySyncedBinding) 23 | model.LastEntityId(8, 1967687883385423038) 24 | model.LastIndexId(4, 3414034888235702623) 25 | model.LastRelationId(6, 3119566795324383223) 26 | 27 | return model 28 | } 29 | -------------------------------------------------------------------------------- /test/model/timeseries.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import "time" 20 | 21 | //go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen 22 | 23 | type TSDate struct { 24 | Id uint64 25 | Time time.Time `objectbox:"id-companion,date"` 26 | } 27 | 28 | type TSDateNano struct { 29 | Id uint64 30 | Time time.Time `objectbox:"id-companion,date-nano"` 31 | } 32 | -------------------------------------------------------------------------------- /objectbox/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package objectbox provides a super-fast, light-weight object persistence framework. 3 | 4 | You can define your entity as a standard .go struct, with a comment signalling to generate ObjectBox code 5 | 6 | //go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen 7 | 8 | type Person struct { 9 | Id uint64 `objectbox:"id"` 10 | FirstName string 11 | LastName string 12 | } 13 | 14 | 15 | Now, just init ObjectBox using the generated code (don't forget to errors in your real code, 16 | they are discarded here to keep the example concise) 17 | 18 | ob, _ := objectbox.NewBuilder().Model(ObjectBoxModel()).Build() 19 | defer ob.Close() 20 | 21 | box := BoxForPerson(ob) 22 | 23 | // Create 24 | id, _ := box.Put(&Person{ 25 | FirstName: "Joe", 26 | LastName: "Green", 27 | }) 28 | 29 | // Read 30 | person, _ := box.Get(id) 31 | 32 | // Update 33 | person.LastName = "Black" 34 | box.Put(person) 35 | 36 | // Delete 37 | box.Remove(person) 38 | 39 | 40 | To learn more, see https://golang.objectbox.io/ 41 | */ 42 | package objectbox 43 | -------------------------------------------------------------------------------- /test/build/build.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package build 18 | 19 | import ( 20 | "os/exec" 21 | ) 22 | 23 | // Package builds a single Go package/directory, running `go build path` 24 | func Package(path string) (stdOut []byte, stdErr []byte, err error) { 25 | var cmd = exec.Command("go", "build") 26 | cmd.Dir = path 27 | stdOut, err = cmd.Output() 28 | if ee, ok := err.(*exec.ExitError); ok { 29 | stdErr = ee.Stderr 30 | } 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /test/model/types.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | ) 7 | 8 | // BaseWithDate model 9 | type BaseWithDate struct { 10 | Date int64 `objectbox:"date"` 11 | } 12 | 13 | // BaseWithValue model 14 | type BaseWithValue struct { 15 | Value float64 16 | } 17 | 18 | // decodes the given byte slice as a complex number 19 | func complex128BytesToEntityProperty(dbValue []byte) (complex128, error) { 20 | // NOTE that constructing the decoder each time is inefficient and only serves as an example for the property converters 21 | var b = bytes.NewBuffer(dbValue) 22 | var decoder = gob.NewDecoder(b) 23 | 24 | var value complex128 25 | err := decoder.Decode(&value) 26 | return value, err 27 | } 28 | 29 | // encodes the given complex number as a byte slice 30 | func complex128BytesToDatabaseValue(goValue complex128) ([]byte, error) { 31 | // NOTE that constructing the encoder each time is inefficient and only serves as an example for the property converters 32 | var b bytes.Buffer 33 | var encoder = gob.NewEncoder(&b) 34 | err := encoder.Encode(goValue) 35 | return b.Bytes(), err 36 | } 37 | -------------------------------------------------------------------------------- /examples/tasks/internal/model/task.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2022 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "time" 21 | ) 22 | 23 | //go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen 24 | 25 | // Put this on a new line to enable sync: // `objectbox:"sync"` 26 | type Task struct { 27 | Id uint64 28 | Text string 29 | DateCreated time.Time `objectbox:"date"` 30 | 31 | // DateFinished is initially set to unix epoch (value 0 in ObjectBox DB) to tag the task as "unfinished" 32 | DateFinished time.Time `objectbox:"date"` 33 | } 34 | -------------------------------------------------------------------------------- /objectbox/entity.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | // Entity is used to specify model in the generated binding code 20 | type Entity struct { 21 | Id TypeId 22 | } 23 | 24 | // this is used internally to automatically synchronize 25 | // note that it must be a singleton 26 | type entity struct { 27 | objectBox *ObjectBox 28 | id TypeId 29 | name string 30 | binding ObjectBinding 31 | 32 | // whether this entity has any relations (standalone or property-rels) - configured during model creation 33 | hasRelations bool 34 | } 35 | -------------------------------------------------------------------------------- /examples/tutorial/README.md: -------------------------------------------------------------------------------- 1 | # Quick setup for ObjectBox Go 2 | 3 | This readme is based on our corresponding blog article, read it [here](https://objectbox.io/how-to-set-up-objectbox-go-on-raspberry-pi/)! 4 | 5 | ## Installation 6 | 7 | The scripts and sources in this directory are needed to setup ObjectBox Go as easily as possible. 8 | To get started right away, just execute `./setup.sh` in your shell and you're basically done. 9 | This command creates the following subdirectories in your home directory: 10 | 11 | - `goroot` with the binaries of Go 1.12.9 (only if Go wasn't installed before or the installed version was <1.12) 12 | - `objectbox` with the shell script `update-objectbox.sh` you can execute to easily update ObjectBox upon a new release 13 | - `projects/objectbox-go-test` mainly with the file `main.go` (also part of this directory) which contains a tiny demo application, based on the Tasks example found in ObjectBox GitHub repo 14 | 15 | ## Working with ObjectBox Go 16 | 17 | For the following commands to work, your current working directory needs to be `~/projects/objectbox-go-test`. 18 | 19 | When you have changed your database model, execute `go generate ./...` 20 | 21 | Whenever you'd like to rebuild and run your entire Go program, run the following two commands: 22 | 23 | go build 24 | ./objectbox-go-test 25 | -------------------------------------------------------------------------------- /examples/tasks/internal/model/objectbox-model.json: -------------------------------------------------------------------------------- 1 | { 2 | "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", 3 | "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", 4 | "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", 5 | "entities": [ 6 | { 7 | "id": "1:6645479796472661392", 8 | "lastPropertyId": "5:6240065879507520219", 9 | "name": "Task", 10 | "properties": [ 11 | { 12 | "id": "1:9211738071025439652", 13 | "name": "Id", 14 | "type": 6, 15 | "flags": 1 16 | }, 17 | { 18 | "id": "2:8804670454579230281", 19 | "name": "Text", 20 | "type": 9 21 | }, 22 | { 23 | "id": "4:1260602348787983453", 24 | "name": "DateCreated", 25 | "type": 10 26 | }, 27 | { 28 | "id": "5:6240065879507520219", 29 | "name": "DateFinished", 30 | "type": 10 31 | } 32 | ] 33 | } 34 | ], 35 | "lastEntityId": "1:6645479796472661392", 36 | "lastIndexId": "", 37 | "lastRelationId": "", 38 | "modelVersion": 5, 39 | "modelVersionParserMinimum": 5, 40 | "retiredEntityUids": [], 41 | "retiredIndexUids": [], 42 | "retiredPropertyUids": [ 43 | 6707341922395832766 44 | ], 45 | "retiredRelationUids": [], 46 | "version": 1 47 | } -------------------------------------------------------------------------------- /.gitlab/merge_request_templates/Default.md: -------------------------------------------------------------------------------- 1 | ## What does this merge request do? 2 | 3 | TODO Link associated issue from title, like: ` #NUMBER` 4 | 5 | TODO Briefly list what this merge request is about 6 | 7 | ## Author's checklist 8 | 9 | - [ ] This merge request fully addresses the requirements of the associated task 10 | - [ ] I did a self-review of the changes and did not spot any issues, among others: 11 | - I added unit tests for new or changed behavior; existing and new tests pass 12 | - My code conforms to our coding standards and guidelines 13 | - My changes are prepared (focused commits, good messages) so reviewing them is easy for the reviewer 14 | - [ ] I amended the [changelog](/CHANGELOG.md) if this affects users in any way 15 | - [ ] I assigned a reviewer to request review 16 | 17 | ## Reviewer's checklist 18 | 19 | - [ ] I reviewed all changes line-by-line and addressed relevant issues. However: 20 | - for quickly resolved issues, I considered creating a fixup commit and discussing that, and 21 | - instead of many or long comments, I considered a meeting with or a draft commit for the author. 22 | - [ ] The requirements of the associated task are fully met 23 | - [ ] I can confirm that: 24 | - CI passes 25 | - If applicable, coverage percentages do not decrease 26 | - New code conforms to standards and guidelines 27 | - If applicable, additional checks were done for special code changes (e.g. core performance, binary size, OSS licenses) 28 | -------------------------------------------------------------------------------- /test/performance/perf/objectbox-model.json: -------------------------------------------------------------------------------- 1 | { 2 | "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", 3 | "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", 4 | "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", 5 | "entities": [ 6 | { 7 | "id": "1:1737161401460991620", 8 | "lastPropertyId": "5:8933082277725371577", 9 | "name": "Entity", 10 | "properties": [ 11 | { 12 | "id": "1:7373286741377356014", 13 | "name": "ID", 14 | "type": 6, 15 | "flags": 1 16 | }, 17 | { 18 | "id": "2:4837914178321008766", 19 | "name": "Int32", 20 | "type": 5 21 | }, 22 | { 23 | "id": "3:3841825182616422591", 24 | "name": "Int64", 25 | "type": 6 26 | }, 27 | { 28 | "id": "4:6473251296493454829", 29 | "name": "String", 30 | "type": 9 31 | }, 32 | { 33 | "id": "5:8933082277725371577", 34 | "name": "Float64", 35 | "type": 8 36 | } 37 | ] 38 | } 39 | ], 40 | "lastEntityId": "1:1737161401460991620", 41 | "lastIndexId": "", 42 | "lastRelationId": "", 43 | "modelVersion": 5, 44 | "modelVersionParserMinimum": 5, 45 | "retiredEntityUids": [], 46 | "retiredIndexUids": [], 47 | "retiredPropertyUids": [], 48 | "retiredRelationUids": null, 49 | "version": 1 50 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | :rotating_light: First, please check: 11 | - existing issues (including closed ones), 12 | - Docs https://golang.objectbox.io/ 13 | - FAQ page https://golang.objectbox.io/faq 14 | 15 | #### Description 16 | A clear and concise description in English of what the issue is. 17 | 18 | #### Basic info 19 | Please complete the following information: 20 | - ObjectBox version (are you using the latest version?): [e.g. v1.1.2] 21 | - Reproducibility: [e.g. occurred once only | occasionally without visible pattern | always] 22 | - Device: [e.g. Desktop] 23 | - OS: [e.g. Ubuntu 20.04] 24 | 25 | #### How to reproduce 26 | Steps to reproduce the behavior: 27 | 1. Put '...' 28 | 2. Make changes to '....' 29 | 3. See error 30 | 31 | #### Expected behavior 32 | A clear and concise description of what you expected to happen. 33 | 34 | #### Code 35 | If applicable, add code to help explain your problem. 36 | - Include affected entity structs. 37 | - Please remove any unnecessary or confidential parts. 38 | - At best, link to or attach a project with a failing test. 39 | 40 | #### Logs, stack traces 41 | If applicable, add relevant logs, or a stack trace. 42 | 43 | #### Additional context 44 | Add any other context about the problem here. 45 | - Is there anything special about your code? 46 | - May transactions or multi-threading play a role? 47 | - Did you find any workarounds to prevent the issue? 48 | -------------------------------------------------------------------------------- /test/model/iot/model.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package iot 18 | 19 | //go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen 20 | 21 | // Event model 22 | type Event struct { 23 | Id uint64 `objectbox:"id"` 24 | Uid string `objectbox:"unique"` 25 | Device string 26 | Date int64 `objectbox:"date"` 27 | Picture []byte 28 | } 29 | 30 | // Reading model 31 | type Reading struct { 32 | Id uint64 `objectbox:"id"` 33 | Date int64 `objectbox:"date"` 34 | 35 | /// to-one relation 36 | EventId uint64 `objectbox:"link:Event"` 37 | 38 | ValueName string 39 | 40 | /// Device sensor data value 41 | ValueString string 42 | 43 | /// Device sensor data value 44 | ValueInteger int64 45 | 46 | /// Device sensor data value 47 | ValueFloating float64 48 | 49 | /// Device sensor data value 50 | ValueInt32 int32 51 | 52 | /// Device sensor data value 53 | ValueFloating32 float32 54 | } 55 | -------------------------------------------------------------------------------- /test/performance/perf_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "io/ioutil" 21 | "log" 22 | "os" 23 | "testing" 24 | ) 25 | 26 | func TestPerformanceSimple(t *testing.T) { 27 | var count = 100000 28 | 29 | if testing.Short() { 30 | count = 1000 31 | } 32 | 33 | log.Printf("running the test with %d objects", count) 34 | 35 | // Test in a temporary directory - if tested by an end user, the repo is read-only. 36 | tempDir, err := ioutil.TempDir("", "objectbox-test") 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | defer func() { 41 | if err := os.RemoveAll(tempDir); err != nil { 42 | t.Fatal(err) 43 | } 44 | }() 45 | 46 | executor := createExecutor(tempDir) 47 | defer executor.close() 48 | 49 | inserts := executor.prepareData(count) 50 | 51 | executor.putAsync(inserts) 52 | executor.removeAll() 53 | 54 | executor.putMany(inserts) 55 | 56 | items := executor.readAll(count) 57 | executor.changeValues(items) 58 | executor.updateAll(items) 59 | 60 | executor.printTimes([]string{}) 61 | } 62 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - gatekeeper 3 | - test 4 | 5 | .test: 6 | stage: test 7 | script: 8 | - ./build/ci.sh 9 | 10 | .test:linux:x64: 11 | extends: .test 12 | tags: [ x64, linux, docker ] 13 | image: golang:$GOVERSION 14 | 15 | gatekeeper-test: 16 | extends: .test:linux:x64 17 | stage: gatekeeper 18 | variables: 19 | GOVERSION: '1.17' 20 | 21 | test:linux:x64:old: 22 | extends: .test:linux:x64 23 | parallel: 24 | matrix: 25 | # Removed Go 1.11.4 here, as it results in linker error with ObjectBox 4.1.0 (and probably before): 26 | # "libobjectbox.so: undefined reference to `logf@GLIBC_2.27'" 27 | - GOVERSION: [ '1.12' ] 28 | BUILD_ARGS: -test.short 29 | before_script: 30 | - rm go.sum # issues with checksum mismatch, if anyone still uses old Go, they may need to delete go.sum 31 | 32 | test:linux:x64: 33 | extends: .test:linux:x64 34 | parallel: 35 | matrix: 36 | - GOVERSION: [ '1.13', '1.14', '1.15', '1.16', '1.17', '1.18', '1.19', '1.20', '1.21', '1.22', '1.23', '1.24' ] 37 | 38 | # TODO Not working on shell runner (e.g. with default (old) version): investigate and find a working setup 39 | test:linux:ARMv7hf: 40 | extends: .test 41 | tags: [ armv7hf, linux, shell ] 42 | variables: 43 | BUILD_ARGS: -test.short 44 | 45 | test:linux:aarch64: 46 | extends: .test 47 | tags: [ arm64, linux, docker ] 48 | image: golang:$GOVERSION 49 | variables: 50 | GOVERSION: '1.22' 51 | BUILD_ARGS: -test.short 52 | 53 | test:mac:x64: 54 | allow_failure: true # TODO Investigate and fix 55 | extends: .test 56 | 57 | tags: [ x64, mac, go ] 58 | 59 | test:win:x64: 60 | allow_failure: true # TODO gcc not found 61 | extends: .test 62 | tags: [ x64, windows, go ] 63 | before_script: 64 | - rm go.sum # issues with checksum mismatch, if anyone still uses old Go, they may need to delete go.sum -------------------------------------------------------------------------------- /test/performance/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "log" 22 | "os" 23 | "runtime" 24 | "runtime/debug" 25 | ) 26 | 27 | func main() { 28 | var dbName = flag.String("db", "db", "database directory") 29 | var count = flag.Int("count", 100000, "number of objects") 30 | var runs = flag.Int("runs", 30, "number of times the tests should be executed") 31 | flag.Parse() 32 | 33 | log.Printf("running the test %d times with %d objects", *runs, *count) 34 | 35 | // remove old database in case it already exists (and remove it after the test as well) 36 | os.RemoveAll(*dbName) 37 | defer os.RemoveAll(*dbName) 38 | 39 | // disable automatic garbage collector 40 | debug.SetGCPercent(-1) 41 | 42 | executor := createExecutor(*dbName) 43 | defer executor.close() 44 | 45 | inserts := executor.prepareData(*count) 46 | 47 | for i := 0; i < *runs; i++ { 48 | executor.putMany(inserts) 49 | items := executor.readAll(*count) 50 | executor.changeValues(items) 51 | executor.updateAll(items) 52 | executor.removeAll() 53 | 54 | log.Printf("%d/%d finished", i+1, *runs) 55 | 56 | // manually invoke GC out of benchmarked time 57 | runtime.GC() 58 | log.Printf("%d/%d garbage-collector executed", i+1, *runs) 59 | } 60 | 61 | executor.printTimes([]string{ 62 | "putMany", 63 | "readAll", 64 | "updateAll", 65 | "removeAll", 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /objectbox/datavisitorc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | // This file implements externs defined in datavisitor.go. 20 | // It needs to be separate or it would cause duplicate symbol errors during linking. 21 | // See https://golang.org/cmd/cgo/#hdr-C_references_to_Go for more details. 22 | 23 | /* 24 | #include <stdbool.h> 25 | #include <stdint.h> 26 | */ 27 | import "C" 28 | import ( 29 | "fmt" 30 | "unsafe" 31 | ) 32 | 33 | // "Implements" the C function obx_data_visitor and dispatches to the registered Go data visitor. 34 | // This function finds the data visitor (based on the pointer to the visitorId) and calls it with the given data 35 | // NOTE: don't change ptr contents, it's `const void*` in C but go doesn't support const pointers 36 | // 37 | //export dataVisitorDispatch 38 | func dataVisitorDispatch(data unsafe.Pointer, size C.size_t, userData unsafe.Pointer) C.bool { 39 | if userData == nil { 40 | panic("Internal error: visitor ID pointer is nil") 41 | } 42 | var visitorId = *(*uint32)(userData) 43 | 44 | // create an empty byte slice and map the C data to it, no copy required 45 | var bytes []byte 46 | if data != nil { 47 | cVoidPtrToByteSlice(data, int(size), &bytes) 48 | } 49 | 50 | var fn = dataVisitorLookup(visitorId) 51 | if fn == nil { 52 | panic(fmt.Sprintf("Internal error: no data visitor found for ID %d", visitorId)) 53 | } 54 | 55 | return C.bool(fn(bytes)) 56 | } 57 | -------------------------------------------------------------------------------- /objectbox/fbutils/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Package fbutils provides utilities for the FlatBuffers in ObjectBox 18 | package fbutils 19 | 20 | import "github.com/google/flatbuffers/go" 21 | 22 | // CreateStringOffset creates an offset in the FlatBuffers table 23 | func CreateStringOffset(fbb *flatbuffers.Builder, value string) flatbuffers.UOffsetT { 24 | return fbb.CreateString(value) 25 | } 26 | 27 | // CreateByteVectorOffset creates an offset in the FlatBuffers table 28 | func CreateByteVectorOffset(fbb *flatbuffers.Builder, value []byte) flatbuffers.UOffsetT { 29 | if value == nil { 30 | return 0 31 | } 32 | 33 | return fbb.CreateByteVector(value) 34 | } 35 | 36 | // CreateStringVectorOffset creates an offset in the FlatBuffers table 37 | func CreateStringVectorOffset(fbb *flatbuffers.Builder, values []string) flatbuffers.UOffsetT { 38 | if values == nil { 39 | return 0 40 | } 41 | 42 | var offsets = make([]flatbuffers.UOffsetT, len(values)) 43 | for i, v := range values { 44 | offsets[i] = fbb.CreateString(v) 45 | } 46 | 47 | return createOffsetVector(fbb, offsets) 48 | } 49 | 50 | func createOffsetVector(fbb *flatbuffers.Builder, offsets []flatbuffers.UOffsetT) flatbuffers.UOffsetT { 51 | fbb.StartVector(int(flatbuffers.SizeUOffsetT), len(offsets), int(flatbuffers.SizeUOffsetT)) 52 | for i := len(offsets) - 1; i >= 0; i-- { 53 | fbb.PrependUOffsetT(offsets[i]) 54 | } 55 | return fbb.EndVector(len(offsets)) 56 | } 57 | -------------------------------------------------------------------------------- /objectbox/c-callbacks-c.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | // This file implements externs defined in c-callbacks.go. 20 | // It needs to be separate or it would cause duplicate symbol errors during linking. 21 | // See https://golang.org/cmd/cgo/#hdr-C_references_to_Go for more details. 22 | 23 | /* 24 | #include <stdbool.h> 25 | #include <stdint.h> 26 | */ 27 | import "C" 28 | import ( 29 | "unsafe" 30 | ) 31 | 32 | // These functions find the callback based on the pointer to the callbackId and call it. 33 | 34 | //export cVoidCallbackDispatch 35 | func cVoidCallbackDispatch(callbackIdPtr C.uintptr_t) { 36 | var callback = cCallbackLookup(callbackIdPtr) 37 | if callback != nil { 38 | callback.callVoid() 39 | } 40 | } 41 | 42 | //export cVoidUint64CallbackDispatch 43 | func cVoidUint64CallbackDispatch(callbackIdPtr C.uintptr_t, arg uint64) { 44 | var callback = cCallbackLookup(callbackIdPtr) 45 | if callback != nil { 46 | callback.callVoidUint64(arg) 47 | } 48 | } 49 | 50 | //export cVoidInt64CallbackDispatch 51 | func cVoidInt64CallbackDispatch(callbackIdPtr C.uintptr_t, arg int64) { 52 | var callback = cCallbackLookup(callbackIdPtr) 53 | if callback != nil { 54 | callback.callVoidInt64(arg) 55 | } 56 | } 57 | 58 | //export cVoidConstVoidCallbackDispatch 59 | func cVoidConstVoidCallbackDispatch(callbackIdPtr C.uintptr_t, arg unsafe.Pointer) { 60 | var callback = cCallbackLookup(callbackIdPtr) 61 | if callback != nil { 62 | callback.callVoidConstVoid(arg) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /cmd/objectbox-gogen/objectbox-gogen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Generates objectbox related code for ObjectBox entities (Go structs) 19 | 20 | It can be used by adding `//go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen` comment inside a .go file 21 | containing the struct that you want to persist and executing `go generate` in the module 22 | 23 | Alternatively, you can run the command manually: 24 | 25 | objectbox-gogen [flags] {source-file} 26 | to generate the binding code 27 | 28 | or 29 | 30 | objectbox-gogen clean {path} 31 | to remove the generated files instead of creating them - this removes *.obx.go and objectbox-model.go but keeps objectbox-model.json 32 | 33 | path: 34 | - a source file path or a valid path pattern as accepted by the go tool (e.g. ./...) 35 | - if not given, the generator expects GOFILE environment variable to be set 36 | 37 | Available flags: 38 | 39 | -byValue 40 | getters should return a struct value (a copy) instead of a struct pointer 41 | -help 42 | print this help 43 | -out string 44 | output path for generated source files 45 | -persist string 46 | path to the model information persistence file (JSON) 47 | -version 48 | print the generator version info 49 | 50 | To learn more about different configuration and annotations for entities, see docs at https://golang.objectbox.io/ 51 | */ 52 | package main 53 | 54 | import gogen "github.com/objectbox/objectbox-generator/v4/cmd/objectbox-gogen" 55 | 56 | func main() { 57 | gogen.Main() 58 | } 59 | -------------------------------------------------------------------------------- /test/embedding_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/objectbox/objectbox-go/test/assert" 23 | "github.com/objectbox/objectbox-go/test/model" 24 | ) 25 | 26 | func TestStructEmbedding(t *testing.T) { 27 | var env = model.NewTestEnv(t) 28 | defer env.Close() 29 | 30 | box := model.BoxForTestEntityInline(env.ObjectBox) 31 | 32 | entity := &model.TestEntityInline{ 33 | BaseWithDate: model.BaseWithDate{ 34 | Date: 4, 35 | }, 36 | BaseWithValue: &model.BaseWithValue{ 37 | Value: 2, 38 | }, 39 | } 40 | 41 | id, err := box.Put(entity) 42 | assert.NoErr(t, err) 43 | assert.Eq(t, id, entity.Id) 44 | 45 | read, err := box.Get(id) 46 | assert.NoErr(t, err) 47 | assert.Eq(t, entity.Id, read.Id) 48 | assert.Eq(t, entity.Date, read.Date) 49 | assert.Eq(t, entity.Value, read.Value) 50 | } 51 | 52 | func TestStructEmbeddingNilPtr(t *testing.T) { 53 | var env = model.NewTestEnv(t) 54 | defer env.Close() 55 | 56 | box := model.BoxForTestEntityInline(env.ObjectBox) 57 | 58 | entity := &model.TestEntityInline{ 59 | BaseWithValue: nil, 60 | } 61 | id, err := box.Put(entity) 62 | assert.NoErr(t, err) 63 | assert.Eq(t, id, entity.Id) 64 | 65 | read, err := box.Get(id) 66 | assert.NoErr(t, err) 67 | assert.True(t, read != nil) 68 | // TODO invert the condition - should be nil 69 | // Currently, the generated Load() method creates the containing object regardless if the given slot is present in 70 | // FlatBuffers. That could be improved by constructing nil-able embedded structs similar to relations, setting to 71 | // nil if none of the slots was set. 72 | assert.True(t, read.BaseWithValue != nil) 73 | } 74 | -------------------------------------------------------------------------------- /test/byvalue_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/objectbox/objectbox-go/test/assert" 23 | "github.com/objectbox/objectbox-go/test/model" 24 | ) 25 | 26 | func TestEntityByValue(t *testing.T) { 27 | env := model.NewTestEnv(t) 28 | defer env.Close() 29 | 30 | box := model.BoxForEntityByValue(env.ObjectBox) 31 | 32 | var object = &model.EntityByValue{} 33 | 34 | id, err := box.Put(object) 35 | assert.NoErr(t, err) 36 | assert.Eq(t, uint64(1), id) 37 | 38 | fetched, err := box.Get(id) 39 | assert.NoErr(t, err) 40 | assert.Eq(t, id, fetched.Id) 41 | 42 | var object2 = &model.EntityByValue{} 43 | id2, err := box.PutAsync(object2) 44 | assert.NoErr(t, err) 45 | assert.Eq(t, uint64(2), id2) 46 | 47 | assert.NoErr(t, env.ObjectBox.AwaitAsyncCompletion()) 48 | 49 | var objects []model.EntityByValue 50 | 51 | objects, err = box.GetAll() 52 | assert.NoErr(t, err) 53 | assert.Eq(t, 2, len(objects)) 54 | assert.Eq(t, id, objects[0].Id) 55 | assert.Eq(t, id2, objects[1].Id) 56 | 57 | err = box.Remove(object2) 58 | assert.NoErr(t, err) 59 | 60 | count, err := box.Count() 61 | assert.NoErr(t, err) 62 | assert.Eq(t, uint64(1), count) 63 | 64 | objects = []model.EntityByValue{{}, {}} 65 | ids, err := box.PutMany(objects) 66 | assert.NoErr(t, err) 67 | assert.Eq(t, len(objects), len(ids)) 68 | assert.Eq(t, uint64(0), objects[0].Id) 69 | assert.Eq(t, uint64(0), objects[1].Id) 70 | assert.Eq(t, uint64(3), ids[0]) 71 | assert.Eq(t, uint64(4), ids[1]) 72 | 73 | count, err = box.Count() 74 | assert.NoErr(t, err) 75 | assert.Eq(t, uint64(3), count) 76 | 77 | objects, err = box.Query().Find() 78 | assert.NoErr(t, err) 79 | assert.Eq(t, 3, len(objects)) 80 | assert.Eq(t, uint64(1), objects[0].Id) 81 | assert.Eq(t, uint64(3), objects[1].Id) 82 | assert.Eq(t, uint64(4), objects[2].Id) 83 | } 84 | -------------------------------------------------------------------------------- /test/tx_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "errors" 21 | "testing" 22 | 23 | "github.com/objectbox/objectbox-go/test/assert" 24 | "github.com/objectbox/objectbox-go/test/model/iot" 25 | ) 26 | 27 | func TestTransactionMassiveInsert(t *testing.T) { 28 | env := iot.NewTestEnv() 29 | defer env.Close() 30 | 31 | var box = iot.BoxForEvent(env.ObjectBox) 32 | 33 | assert.NoErr(t, box.RemoveAll()) 34 | 35 | var insert = uint64(1000000) 36 | 37 | if testing.Short() { 38 | insert = 1000 39 | } 40 | 41 | assert.NoErr(t, env.RunInWriteTx(func() error { 42 | for i := insert; i > 0; i-- { 43 | _, err := box.Put(&iot.Event{}) 44 | assert.NoErr(t, err) 45 | } 46 | return nil 47 | })) 48 | 49 | count, err := box.Count() 50 | assert.NoErr(t, err) 51 | assert.Eq(t, insert, count) 52 | } 53 | 54 | func TestTransactionRollback(t *testing.T) { 55 | env := iot.NewTestEnv() 56 | defer env.Close() 57 | 58 | var box = iot.BoxForEvent(env.ObjectBox) 59 | 60 | assert.NoErr(t, box.RemoveAll()) 61 | 62 | var insert = make([]*iot.Event, 100) 63 | for i := 0; i < len(insert); i++ { 64 | insert[i] = &iot.Event{} 65 | } 66 | 67 | _, err := box.PutMany(insert) 68 | assert.NoErr(t, err) 69 | 70 | count, err := box.Count() 71 | assert.NoErr(t, err) 72 | assert.Eq(t, len(insert), int(count)) 73 | 74 | // rolled-back Tx 75 | var expected = errors.New("expected") 76 | assert.Eq(t, expected, env.RunInWriteTx(func() error { 77 | assert.NoErr(t, box.RemoveAll()) 78 | return expected 79 | })) 80 | 81 | count, err = box.Count() 82 | assert.NoErr(t, err) 83 | assert.Eq(t, len(insert), int(count)) 84 | 85 | // successful tx 86 | assert.NoErr(t, env.RunInWriteTx(func() error { 87 | assert.NoErr(t, box.RemoveAll()) 88 | return nil 89 | })) 90 | 91 | count, err = box.Count() 92 | assert.NoErr(t, err) 93 | assert.Eq(t, 0, int(count)) 94 | 95 | } 96 | -------------------------------------------------------------------------------- /test/model/entity.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import "time" 20 | 21 | //go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen 22 | 23 | // Entity model for tests 24 | type Entity struct { 25 | // base types 26 | Id uint64 27 | Int int 28 | Int8 int8 29 | Int16 int16 30 | Int32 int32 31 | Int64 int64 32 | Uint uint 33 | Uint8 uint8 34 | Uint16 uint16 35 | Uint32 uint32 36 | Uint64 uint64 37 | Bool bool 38 | String string 39 | StringVector []string 40 | Byte byte 41 | ByteVector []byte 42 | Rune rune 43 | Float32 float32 44 | Float64 float64 45 | 46 | // converters 47 | Date time.Time `objectbox:"date"` 48 | Complex128 complex128 `objectbox:"type:[]byte converter:complex128Bytes"` 49 | 50 | // one-to-many relations 51 | Related TestEntityRelated `objectbox:"link"` 52 | RelatedPtr *TestEntityRelated `objectbox:"link"` 53 | RelatedPtr2 *TestEntityRelated `objectbox:"link"` 54 | 55 | // many-to-many relations 56 | RelatedSlice []EntityByValue 57 | RelatedPtrSlice []*TestEntityRelated `objectbox:"lazy"` 58 | 59 | IntPtr *int 60 | Int8Ptr *int8 61 | Int16Ptr *int16 62 | Int32Ptr *int32 63 | Int64Ptr *int64 64 | UintPtr *uint 65 | Uint8Ptr *uint8 66 | Uint16Ptr *uint16 67 | Uint32Ptr *uint32 68 | Uint64Ptr *uint64 69 | BoolPtr *bool 70 | StringPtr *string 71 | StringVectorPtr *[]string 72 | BytePtr *byte 73 | ByteVectorPtr *[]byte 74 | RunePtr *rune 75 | Float32Ptr *float32 76 | Float64Ptr *float64 77 | } 78 | 79 | // TestStringIdEntity model 80 | type TestStringIdEntity struct { 81 | Id string `objectbox:"id(assignable)"` // id(assignable) also works with integer IDs, but let's test this "harder" case 82 | } 83 | 84 | // TestEntityInline model 85 | type TestEntityInline struct { 86 | BaseWithDate `objectbox:"inline"` 87 | *BaseWithValue `objectbox:"inline"` 88 | 89 | Id uint64 90 | } 91 | 92 | // TestEntityRelated model 93 | type TestEntityRelated struct { 94 | Id uint64 95 | Name string 96 | 97 | // have another level of relations 98 | Next *EntityByValue `objectbox:"link"` 99 | NextSlice []EntityByValue 100 | } 101 | 102 | // TestEntitySynced model 103 | // `objectbox:"sync"` 104 | type TestEntitySynced struct { 105 | Id uint64 106 | Name string 107 | } 108 | -------------------------------------------------------------------------------- /test/model/iot/objectbox-model.json: -------------------------------------------------------------------------------- 1 | { 2 | "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", 3 | "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", 4 | "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", 5 | "entities": [ 6 | { 7 | "id": "1:1468539308767086854", 8 | "lastPropertyId": "5:6024563395733984005", 9 | "name": "Event", 10 | "properties": [ 11 | { 12 | "id": "1:3098166604415018001", 13 | "name": "Id", 14 | "type": 6, 15 | "flags": 1 16 | }, 17 | { 18 | "id": "2:1213411729427304641", 19 | "name": "Device", 20 | "type": 9 21 | }, 22 | { 23 | "id": "3:5907655274386702697", 24 | "name": "Date", 25 | "type": 10 26 | }, 27 | { 28 | "id": "4:472416569173577818", 29 | "name": "Uid", 30 | "indexId": "1:3297791712577314158", 31 | "type": 9, 32 | "flags": 2080 33 | }, 34 | { 35 | "id": "5:6024563395733984005", 36 | "name": "Picture", 37 | "type": 23 38 | } 39 | ] 40 | }, 41 | { 42 | "id": "2:5284076134434938613", 43 | "lastPropertyId": "9:6040892611651481730", 44 | "name": "Reading", 45 | "properties": [ 46 | { 47 | "id": "1:3968063745680890327", 48 | "name": "Id", 49 | "type": 6, 50 | "flags": 1 51 | }, 52 | { 53 | "id": "2:4852407661923085028", 54 | "name": "Date", 55 | "type": 10 56 | }, 57 | { 58 | "id": "3:1403806151574554320", 59 | "name": "EventId", 60 | "indexId": "2:2642563953244304959", 61 | "type": 11, 62 | "flags": 520, 63 | "relationTarget": "Event" 64 | }, 65 | { 66 | "id": "4:5626221656121286670", 67 | "name": "ValueName", 68 | "type": 9 69 | }, 70 | { 71 | "id": "5:7303099924122013060", 72 | "name": "ValueString", 73 | "type": 9 74 | }, 75 | { 76 | "id": "6:1404333021836291657", 77 | "name": "ValueInteger", 78 | "type": 6 79 | }, 80 | { 81 | "id": "7:7102253623343671118", 82 | "name": "ValueFloating", 83 | "type": 8 84 | }, 85 | { 86 | "id": "8:7566830186276557216", 87 | "name": "ValueInt32", 88 | "type": 5 89 | }, 90 | { 91 | "id": "9:6040892611651481730", 92 | "name": "ValueFloating32", 93 | "type": 7 94 | } 95 | ] 96 | } 97 | ], 98 | "lastEntityId": "2:5284076134434938613", 99 | "lastIndexId": "2:2642563953244304959", 100 | "lastRelationId": "", 101 | "modelVersion": 5, 102 | "modelVersionParserMinimum": 5, 103 | "retiredEntityUids": [], 104 | "retiredIndexUids": [], 105 | "retiredPropertyUids": [], 106 | "retiredRelationUids": null, 107 | "version": 1 108 | } -------------------------------------------------------------------------------- /objectbox/datavisitor.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | /* 20 | This file implements obx_data_visitor forwarding to Go callbacks 21 | 22 | Overview: 23 | * Register a dataVisitor callback, getting a visitor ID. 24 | * Pass the registered visitor ID together with a generic dataVisitor (C.dataVisitorDispatch) to a C.obx_* function. 25 | * When ObjectBox calls dataVisitorDispatch, it finds the callback registered under that ID and calls it. 26 | * After there can be no more callbacks, the visitor must be unregistered. 27 | 28 | Code example: 29 | var visitorId uint32 30 | visitorId, err = dataVisitorRegister(func(bytes []byte) bool { 31 | // do your thing with the data 32 | object := Cursor.binding.Load(bytes) 33 | return true // this return value is passed back to the ObjectBox, usually used to break the traversal 34 | }) 35 | 36 | if err != nil { 37 | return err 38 | } 39 | 40 | // don't forget to unregister the visitor after it's no longer going to be called or you would fill the queue up quickly 41 | defer dataVisitorUnregister(visitorId) 42 | 43 | rc := C.obx_query_visit(cQuery, cCursor, dataVisitor, unsafe.Pointer(&visitorId), C.uint64_t(offset), C.uint64_t(limit)) 44 | */ 45 | 46 | /* 47 | #include "objectbox.h" 48 | 49 | // this implements the obx_data_visitor forwarding, it's called from ObjectBox C-api (see `dataVisitor` go var) 50 | extern bool dataVisitorDispatch(void* visitorId, void* data, size_t size); 51 | */ 52 | import "C" 53 | import ( 54 | "fmt" 55 | "sync" 56 | "unsafe" 57 | ) 58 | 59 | type dataVisitorCallback = func([]byte) bool 60 | 61 | var dataVisitor = (*C.obx_data_visitor)(unsafe.Pointer(C.dataVisitorDispatch)) 62 | var dataVisitorId uint32 63 | var dataVisitorMutex sync.Mutex 64 | var dataVisitorCallbacks = make(map[uint32]dataVisitorCallback) 65 | 66 | func dataVisitorRegister(fn dataVisitorCallback) (uint32, error) { 67 | dataVisitorMutex.Lock() 68 | defer dataVisitorMutex.Unlock() 69 | 70 | // cycle through ids until we find an empty slot 71 | dataVisitorId++ 72 | var initialId = dataVisitorId 73 | for dataVisitorCallbacks[dataVisitorId] != nil { 74 | dataVisitorId++ 75 | 76 | if initialId == dataVisitorId { 77 | return 0, fmt.Errorf("full queue of data-visitor callbacks - can't allocate another") 78 | } 79 | } 80 | 81 | dataVisitorCallbacks[dataVisitorId] = fn 82 | return dataVisitorId, nil 83 | } 84 | 85 | func dataVisitorLookup(id uint32) dataVisitorCallback { 86 | dataVisitorMutex.Lock() 87 | defer dataVisitorMutex.Unlock() 88 | 89 | return dataVisitorCallbacks[id] 90 | } 91 | 92 | func dataVisitorUnregister(id uint32) { 93 | dataVisitorMutex.Lock() 94 | defer dataVisitorMutex.Unlock() 95 | 96 | delete(dataVisitorCallbacks, id) 97 | } 98 | -------------------------------------------------------------------------------- /test/microbench_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "runtime" 23 | "testing" 24 | ) 25 | 26 | // 500000000 3.93 ns/op 27 | func BenchmarkLockOsThread(b *testing.B) { 28 | for n := 0; n < b.N; n++ { 29 | runtime.LockOSThread() 30 | runtime.UnlockOSThread() 31 | } 32 | } 33 | 34 | // prevent compiler optimizing out an unused return value 35 | var globalErr error 36 | 37 | // Panic/Failing 20000000 88.2 ns/op 38 | // Panic/Successful 50000000 36.1 ns/op 39 | // Error/Failing 50000000 31.0 ns/op 40 | // Error/Successful 1000000000 2.14 ns/op 41 | func BenchmarkErrorVsPanicRecover(b *testing.B) { 42 | // Using a single function with boolean switches doesn't hurt the benchmarks. 43 | // Originally I had multiple functions and the results and the results are about the same 44 | var withPanic = func(shouldFail bool) (err error) { 45 | defer func() { 46 | if r := recover(); r != nil { 47 | switch x := r.(type) { 48 | case string: 49 | err = errors.New(x) 50 | case error: 51 | err = x 52 | default: 53 | err = fmt.Errorf("%v", r) 54 | } 55 | } 56 | }() 57 | 58 | if shouldFail { 59 | panic("oh") 60 | } 61 | 62 | return nil 63 | } 64 | var withError = func(shouldFail bool) error { 65 | if shouldFail { 66 | return errors.New("oh") 67 | } 68 | return nil 69 | } 70 | 71 | b.Run("Panic/Failing", func(b *testing.B) { 72 | for n := 0; n < b.N; n++ { 73 | globalErr = withPanic(true) 74 | } 75 | }) 76 | 77 | b.Run("Panic/Successful", func(b *testing.B) { 78 | for n := 0; n < b.N; n++ { 79 | globalErr = withPanic(false) 80 | } 81 | }) 82 | 83 | b.Run("Error/Failing", func(b *testing.B) { 84 | for n := 0; n < b.N; n++ { 85 | globalErr = withError(true) 86 | } 87 | }) 88 | 89 | b.Run("Error/Successful", func(b *testing.B) { 90 | for n := 0; n < b.N; n++ { 91 | globalErr = withError(false) 92 | } 93 | }) 94 | } 95 | 96 | // How heavy it is to return IDs from PutMany(), even if a caller doesn't need them. 97 | func BenchmarkNumberSlice(b *testing.B) { 98 | var tester = func(b *testing.B, size int) { 99 | for n := 0; n < b.N; n++ { 100 | var items = make([]uint64, size) 101 | for i := 0; i < size; i++ { 102 | items[i] = uint64(i) 103 | } 104 | } 105 | } 106 | 107 | b.Run("1k items", func(b *testing.B) { 108 | tester(b, 1000) 109 | }) 110 | 111 | b.Run("10k items", func(b *testing.B) { 112 | tester(b, 10*1000) 113 | }) 114 | 115 | b.Run("1m items", func(b *testing.B) { 116 | tester(b, 1000*1000) 117 | }) 118 | } 119 | -------------------------------------------------------------------------------- /objectbox/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2025 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | /* 20 | #include <stdlib.h> 21 | #include "objectbox.h" 22 | */ 23 | import "C" 24 | import "fmt" 25 | 26 | // Version represents a semantic-version If you depend on a certain version of ObjectBox, you can check using this struct. 27 | // See also VersionGo() and VersionLib(). 28 | type Version struct { 29 | Major int 30 | Minor int 31 | Patch int 32 | Label string 33 | } 34 | 35 | func (v Version) LessThan(other Version) bool { 36 | if v.Major != other.Major { 37 | return v.Major < other.Major 38 | } 39 | if v.Minor != other.Minor { 40 | return v.Minor < other.Minor 41 | } 42 | return v.Patch < other.Patch 43 | } 44 | 45 | func (v Version) GreaterThanOrEqualTo(other Version) bool { 46 | return !v.LessThan(other) 47 | } 48 | 49 | func (v Version) String() string { 50 | versionString := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) 51 | if len(v.Label) > 0 { 52 | versionString += "-" + v.Label 53 | } 54 | return versionString 55 | } 56 | 57 | // VersionGo returns the Version of the ObjectBox-Go binding 58 | func VersionGo() Version { 59 | // for label, use `beta.0` format, increasing the counter for each subsequent release 60 | return Version{1, 9, 0, ""} 61 | } 62 | 63 | // VersionLib returns the Version of the dynamic linked ObjectBox library (loaded at runtime) 64 | func VersionLib() Version { 65 | var major C.int 66 | var minor C.int 67 | var patch C.int 68 | C.obx_version(&major, &minor, &patch) 69 | return Version{int(major), int(minor), int(patch), ""} 70 | } 71 | 72 | // VersionLibStatic returns the Version of ObjectBox library this Go version was compiled against (build time); 73 | // see VersionLib() for the actually loaded version. 74 | // This version is at least VersionLibMinRecommended(). 75 | func VersionLibStatic() Version { 76 | return Version{C.OBX_VERSION_MAJOR, C.OBX_VERSION_MINOR, C.OBX_VERSION_PATCH, ""} 77 | } 78 | 79 | // VersionLibMin returns the minimum Version of the dynamic linked ObjectBox library that is compatible with this Go version 80 | func VersionLibMin() Version { 81 | return Version{4, 1, 0, ""} 82 | } 83 | 84 | // VersionLibMinRecommended returns the minimum recommended Version of the dynamic linked ObjectBox library. 85 | // This version not only considers compatibility with this Go version, but also known issues older (compatible) versions. 86 | // It is guaranteed to be at least VersionLibMin() 87 | func VersionLibMinRecommended() Version { 88 | return Version{4, 2, 0, ""} 89 | } 90 | 91 | // VersionInfo returns a printable version string 92 | func VersionInfo() string { 93 | return "ObjectBox Go version " + VersionGo().String() + " using dynamic library version " + VersionLib().String() 94 | } 95 | 96 | func internalLibVersion() string { 97 | return "C-API v" + C.GoString(C.obx_version_string()) + " core v" + C.GoString(C.obx_version_core_string()) 98 | } 99 | -------------------------------------------------------------------------------- /test/stringid_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "strconv" 21 | "testing" 22 | 23 | "github.com/objectbox/objectbox-go/test/assert" 24 | "github.com/objectbox/objectbox-go/test/model" 25 | ) 26 | 27 | func TestStringIdSingleOps(t *testing.T) { 28 | env := model.NewTestEnv(t) 29 | defer env.Close() 30 | 31 | box := model.BoxForTestStringIdEntity(env.ObjectBox) 32 | 33 | var object = &model.TestStringIdEntity{} 34 | 35 | id, err := box.Put(object) 36 | assert.NoErr(t, err) 37 | assert.Eq(t, uint64(1), id) 38 | assert.Eq(t, strconv.FormatUint(id, 10), object.Id) 39 | 40 | fetched, err := box.Get(id) 41 | assert.NoErr(t, err) 42 | assert.Eq(t, strconv.FormatUint(id, 10), fetched.Id) 43 | 44 | var object2 = &model.TestStringIdEntity{} 45 | id2, err := box.PutAsync(object2) 46 | assert.NoErr(t, err) 47 | assert.Eq(t, uint64(2), id2) 48 | assert.Eq(t, strconv.FormatUint(id2, 10), object2.Id) 49 | 50 | assert.NoErr(t, env.ObjectBox.AwaitAsyncCompletion()) 51 | 52 | all, err := box.GetAll() 53 | assert.NoErr(t, err) 54 | assert.Eq(t, 2, len(all)) 55 | assert.Eq(t, strconv.FormatUint(id, 10), all[0].Id) 56 | assert.Eq(t, strconv.FormatUint(id2, 10), all[1].Id) 57 | 58 | err = box.Remove(object2) 59 | assert.NoErr(t, err) 60 | 61 | count, err := box.Count() 62 | assert.NoErr(t, err) 63 | assert.Eq(t, uint64(1), count) 64 | } 65 | 66 | func TestStringIdMultiOps(t *testing.T) { 67 | env := model.NewTestEnv(t) 68 | defer env.Close() 69 | 70 | box := model.BoxForTestStringIdEntity(env.ObjectBox) 71 | 72 | objects := []*model.TestStringIdEntity{{}, {}} 73 | 74 | ids, err := box.PutMany(objects) 75 | assert.NoErr(t, err) 76 | assert.Eq(t, len(objects), len(ids)) 77 | assert.Eq(t, "1", objects[0].Id) 78 | assert.Eq(t, "2", objects[1].Id) 79 | assert.Eq(t, uint64(1), ids[0]) 80 | assert.Eq(t, uint64(2), ids[1]) 81 | 82 | count, err := box.Count() 83 | assert.NoErr(t, err) 84 | assert.Eq(t, uint64(2), count) 85 | 86 | query := box.Query(model.TestStringIdEntity_.Id.Equals(2)) 87 | found, err := query.Find() 88 | assert.NoErr(t, err) 89 | assert.Eq(t, 1, len(found)) 90 | assert.Eq(t, "2", found[0].Id) 91 | 92 | err = box.RemoveAll() 93 | assert.NoErr(t, err) 94 | 95 | count, err = box.Count() 96 | assert.NoErr(t, err) 97 | assert.Eq(t, uint64(0), count) 98 | } 99 | 100 | func TestSelfAssignedId(t *testing.T) { 101 | env := model.NewTestEnv(t) 102 | defer env.Close() 103 | 104 | box := model.BoxForTestStringIdEntity(env.ObjectBox) 105 | 106 | objects := []*model.TestStringIdEntity{{}, {Id: "10"}} 107 | 108 | ids, err := box.PutMany(objects) 109 | assert.NoErr(t, err) 110 | assert.Eq(t, len(objects), len(ids)) 111 | assert.Eq(t, "1", objects[0].Id) 112 | assert.Eq(t, "10", objects[1].Id) 113 | assert.Eq(t, uint64(1), ids[0]) 114 | assert.Eq(t, uint64(10), ids[1]) 115 | } 116 | -------------------------------------------------------------------------------- /objectbox/fbutils/setters.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fbutils 18 | 19 | import flatbuffers "github.com/google/flatbuffers/go" 20 | 21 | // Setters always write values, regardless of the default 22 | 23 | // SetBoolSlot sets the given value in the FlatBuffers table 24 | func SetBoolSlot(fbb *flatbuffers.Builder, slot int, value bool) { 25 | if value { 26 | SetByteSlot(fbb, slot, 1) 27 | } else { 28 | SetByteSlot(fbb, slot, 0) 29 | } 30 | } 31 | 32 | // SetByteSlot sets the given value in the FlatBuffers table 33 | func SetByteSlot(fbb *flatbuffers.Builder, slot int, value byte) { 34 | fbb.PrependByte(value) 35 | fbb.Slot(slot) 36 | } 37 | 38 | // SetUint8Slot sets the given value in the FlatBuffers table 39 | func SetUint8Slot(fbb *flatbuffers.Builder, slot int, value uint8) { 40 | fbb.PrependUint8(value) 41 | fbb.Slot(slot) 42 | } 43 | 44 | // SetUint16Slot sets the given value in the FlatBuffers table 45 | func SetUint16Slot(fbb *flatbuffers.Builder, slot int, value uint16) { 46 | fbb.PrependUint16(value) 47 | fbb.Slot(slot) 48 | } 49 | 50 | // SetUint32Slot sets the given value in the FlatBuffers table 51 | func SetUint32Slot(fbb *flatbuffers.Builder, slot int, value uint32) { 52 | fbb.PrependUint32(value) 53 | fbb.Slot(slot) 54 | } 55 | 56 | // SetUint64Slot sets the given value in the FlatBuffers table 57 | func SetUint64Slot(fbb *flatbuffers.Builder, slot int, value uint64) { 58 | fbb.PrependUint64(value) 59 | fbb.Slot(slot) 60 | } 61 | 62 | // SetInt8Slot sets the given value in the FlatBuffers table 63 | func SetInt8Slot(fbb *flatbuffers.Builder, slot int, value int8) { 64 | fbb.PrependInt8(value) 65 | fbb.Slot(slot) 66 | } 67 | 68 | // SetInt16Slot sets the given value in the FlatBuffers table 69 | func SetInt16Slot(fbb *flatbuffers.Builder, slot int, value int16) { 70 | fbb.PrependInt16(value) 71 | fbb.Slot(slot) 72 | } 73 | 74 | // SetInt32Slot sets the given value in the FlatBuffers table 75 | func SetInt32Slot(fbb *flatbuffers.Builder, slot int, value int32) { 76 | fbb.PrependInt32(value) 77 | fbb.Slot(slot) 78 | } 79 | 80 | // SetInt64Slot sets the given value in the FlatBuffers table 81 | func SetInt64Slot(fbb *flatbuffers.Builder, slot int, value int64) { 82 | fbb.PrependInt64(value) 83 | fbb.Slot(slot) 84 | } 85 | 86 | // SetFloat32Slot sets the given value in the FlatBuffers table 87 | func SetFloat32Slot(fbb *flatbuffers.Builder, slot int, value float32) { 88 | fbb.PrependFloat32(value) 89 | fbb.Slot(slot) 90 | } 91 | 92 | // SetFloat64Slot sets the given value in the FlatBuffers table 93 | func SetFloat64Slot(fbb *flatbuffers.Builder, slot int, value float64) { 94 | fbb.PrependFloat64(value) 95 | fbb.Slot(slot) 96 | } 97 | 98 | // SetUOffsetTSlot sets the given value in the FlatBuffers table 99 | func SetUOffsetTSlot(fbb *flatbuffers.Builder, slot int, value flatbuffers.UOffsetT) { 100 | if value != 0 { 101 | fbb.PrependUOffsetT(value) 102 | fbb.Slot(slot) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/model/iot/helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package iot 18 | 19 | import ( 20 | "github.com/objectbox/objectbox-go/objectbox" 21 | "github.com/objectbox/objectbox-go/test/assert" 22 | "io/ioutil" 23 | "os" 24 | "strconv" 25 | "testing" 26 | ) 27 | 28 | type TestEnv struct { 29 | *objectbox.ObjectBox 30 | dir string 31 | } 32 | 33 | // Close closes ObjectBox and removes the database 34 | func (env *TestEnv) Close() { 35 | env.ObjectBox.Close() 36 | os.RemoveAll(env.dir) 37 | } 38 | 39 | // NewTestEnv creates an empty ObjectBox instance 40 | func NewTestEnv() *TestEnv { 41 | 42 | // Test in a temporary directory - if tested by an end user, the repo is read-only. 43 | tempDir, err := ioutil.TempDir("", "objectbox-test") 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | return NewTestEnvWithDir(nil, tempDir) 49 | } 50 | 51 | // NewTestEnvWithDir creates an empty ObjectBox instance for a given dir 52 | func NewTestEnvWithDir(t *testing.T, directory string) *TestEnv { 53 | objectBox, err := objectbox.NewBuilder().Directory(directory).Model(ObjectBoxModel()).Build() 54 | if err != nil { 55 | panic(err) 56 | } 57 | if t != nil { 58 | cwd, _ := os.Getwd() 59 | t.Log("Creating store in directory:", directory, "(cwd:", cwd, ")") 60 | } 61 | 62 | return &TestEnv{ 63 | ObjectBox: objectBox, 64 | dir: directory, 65 | } 66 | } 67 | 68 | // PutEvent creates an event 69 | func PutEvent(ob *objectbox.ObjectBox, device string, date int64) *Event { 70 | event := Event{Device: device, Date: date} 71 | id, err := BoxForEvent(ob).Put(&event) 72 | assert.NoErr(nil, err) 73 | event.Id = id 74 | return &event 75 | } 76 | 77 | // PutReading creates a reading 78 | func PutReading(ob *objectbox.ObjectBox, name string, ValueString string, ValueInteger int64, ValueFloating float64, ValueInt32 int32, ValueFloating32 float32) *Reading { 79 | event := Reading{ValueName: name, ValueString: ValueString, ValueInteger: ValueInteger, ValueFloating: ValueFloating, ValueInt32: ValueInt32, ValueFloating32: ValueFloating32} 80 | id, err := BoxForReading(ob).Put(&event) 81 | assert.NoErr(nil, err) 82 | event.Id = id 83 | return &event 84 | } 85 | 86 | // PutEvents creates multiple events 87 | func PutEvents(ob *objectbox.ObjectBox, count int) []*Event { 88 | // TODO TX 89 | events := make([]*Event, 0, count) 90 | for i := 1; i <= count; i++ { 91 | event := PutEvent(ob, "device "+strconv.Itoa(i), int64(10000+i)) 92 | events = append(events, event) 93 | } 94 | return events 95 | } 96 | 97 | // PutReadings creates multiple readings 98 | func PutReadings(ob *objectbox.ObjectBox, count int) []*Reading { 99 | // TODO TX 100 | readings := make([]*Reading, 0, count) 101 | for i := 1; i <= count; i++ { 102 | reading := PutReading(ob, "reading"+strconv.Itoa(i), "string"+strconv.Itoa(i), int64(10000+i), float64(10000+i), int32(10000+i), float32(10000+i)) 103 | readings = append(readings, reading) 104 | } 105 | return readings 106 | } 107 | -------------------------------------------------------------------------------- /objectbox/sync.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2025 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | /* 20 | #include <stdlib.h> 21 | #include "objectbox-sync.h" 22 | */ 23 | import "C" 24 | 25 | // SyncIsAvailable returns true if the loaded ObjectBox native library supports Sync. 26 | // [ObjectBox Sync](https://objectbox.io/sync/) makes data available and synchronized across devices, online and offline. 27 | func SyncIsAvailable() bool { 28 | return bool(C.obx_has_feature(C.OBXFeature_Sync)) 29 | } 30 | 31 | // SyncCredentials are used to authenticate a sync client against a server. 32 | type SyncCredentials struct { 33 | cType C.OBXSyncCredentialsType 34 | data []byte 35 | dataString string 36 | username string 37 | password string 38 | } 39 | 40 | // SyncCredentialsNone - no credentials - usually only for development, with a server configured to accept all 41 | // connections without authentication. 42 | func SyncCredentialsNone() *SyncCredentials { 43 | return &SyncCredentials{ 44 | cType: C.OBXSyncCredentialsType_NONE, 45 | data: nil, 46 | } 47 | } 48 | 49 | // SyncCredentialsSharedSecret - shared secret authentication 50 | func SyncCredentialsSharedSecret(data []byte) *SyncCredentials { 51 | return &SyncCredentials{ 52 | cType: C.OBXSyncCredentialsType_SHARED_SECRET, 53 | data: data, 54 | } 55 | } 56 | 57 | // SyncCredentialsGoogleAuth - Google authentication 58 | func SyncCredentialsGoogleAuth(data []byte) *SyncCredentials { 59 | return &SyncCredentials{ 60 | cType: C.OBXSyncCredentialsType_GOOGLE_AUTH, 61 | data: data, 62 | } 63 | } 64 | 65 | // SyncCredentialsUsernamePassword - authentication with username and password 66 | func SyncCredentialsObxAdminUser(username string, password string) *SyncCredentials { 67 | return &SyncCredentials{ 68 | cType: C.OBXSyncCredentialsType_OBX_ADMIN_USER, 69 | username: username, 70 | password: password, 71 | } 72 | } 73 | 74 | // SyncCredentialsUsernamePassword - authentication with username and password 75 | func SyncCredentialsUsernamePassword(username string, password string) *SyncCredentials { 76 | return &SyncCredentials{ 77 | cType: C.OBXSyncCredentialsType_USER_PASSWORD, 78 | username: username, 79 | password: password, 80 | } 81 | } 82 | 83 | // SyncCredentialsJwtId - JWT authentication with an ID token 84 | func SyncCredentialsJwtId(data string) *SyncCredentials { 85 | return &SyncCredentials{ 86 | cType: C.OBXSyncCredentialsType_JWT_ID, 87 | dataString: data, 88 | } 89 | } 90 | 91 | // SyncCredentialsJwtAccess - JWT authentication with an access token 92 | func SyncCredentialsJwtAccess(data string) *SyncCredentials { 93 | return &SyncCredentials{ 94 | cType: C.OBXSyncCredentialsType_JWT_ACCESS, 95 | dataString: data, 96 | } 97 | } 98 | 99 | // SyncCredentialsJwtRefresh - JWT authentication with a refresh token 100 | func SyncCredentialsJwtRefresh(data string) *SyncCredentials { 101 | return &SyncCredentials{ 102 | cType: C.OBXSyncCredentialsType_JWT_REFRESH, 103 | dataString: data, 104 | } 105 | } 106 | 107 | // SyncCredentialsJwtCustom - JWT authentication with a custom token 108 | func SyncCredentialsJwtCustom(data string) *SyncCredentials { 109 | return &SyncCredentials{ 110 | cType: C.OBXSyncCredentialsType_JWT_CUSTOM, 111 | dataString: data, 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /examples/tutorial/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | GO_VER_NEW=1.12.9 5 | 6 | function fetch_go() { 7 | ARCH=$(uname -m) 8 | if [[ "$ARCH" == "x86_64" ]]; then 9 | ARCH=amd64 10 | elif [[ "$ARCH" == arm* ]]; then 11 | ARCH=armv6l 12 | else 13 | echo "Unsupported architecture: $ARCH" 14 | if [[ "$GO_DIR_MISSING" == "1" ]]; then 15 | rm -rf ~/go 16 | fi 17 | fi 18 | 19 | # download Go 20 | echo "Fetching go $GO_VER_NEW..." 21 | if [ -x "$(command -v curl)" ]; then 22 | curl -L -o go$GO_VER_NEW.linux-$ARCH.tar.gz https://dl.google.com/go/go$GO_VER_NEW.linux-$ARCH.tar.gz 23 | else 24 | wget -q https://dl.google.com/go/go$GO_VER_NEW.linux-$ARCH.tar.gz 25 | fi 26 | goroot=~/goroot 27 | echo "Installing go $GO_VER_NEW into $goroot" 28 | mkdir -p $goroot 29 | tar -C $goroot -xzf go$GO_VER_NEW.linux-$ARCH.tar.gz --strip 1 30 | rm go$GO_VER_NEW.linux-$ARCH.tar.gz 31 | 32 | # comment out old GOROOT or GOPATH exports in bashrc if they already exist 33 | sed -i 's/^export \(GOROOT\|GOPATH\)/#&/' ~/.bashrc 34 | sed -i 's/^export PATH *=.*\$\(GOROOT\|GOPATH\)/#&/' ~/.bashrc 35 | 36 | # set Go's environment variables in bashrc 37 | echo "Setting up ~/go as GOPATH" 38 | mkdir -p ~/go 39 | echo 'export GOROOT=$HOME/goroot' >>~/.bashrc 40 | echo 'export GOPATH=$HOME/go' >>~/.bashrc 41 | echo 'export PATH=$GOROOT/bin:$PATH' >>~/.bashrc 42 | echo "Note: your ~/.bashrc has been adjusted to make Go $GO_VER_NEW be available globally" 43 | echo "Please execute \"source ~/.bashrc\" after this script finishes to make Go available for this session or restart your shell" 44 | 45 | # set these variables again for this session 46 | export GOROOT=$HOME/goroot 47 | export GOPATH=$HOME/go 48 | export PATH=$GOROOT/bin:$PATH 49 | } 50 | 51 | # setup our working directory 52 | cd ~ 53 | mkdir -p projects objectbox 54 | 55 | # check if recent version (>=1.12) of Go is installed, otherwise download it 56 | if [ ! -x "$(command -v go)" ]; then 57 | fetch_go 58 | else 59 | GO_VER=$(go version | cut -d' ' -f3) 60 | GO_VER_MAJOR=$(echo $GO_VER | cut -d'.' -f1 | cut -d'o' -f2) 61 | GO_VER_MINOR=$(echo $GO_VER | cut -d'.' -f2) 62 | if (("$GO_VER_MAJOR" >= "1" && "$GO_VER_MINOR" >= "12")); then 63 | echo "Note: using installed Go with version $GO_VER" 64 | else 65 | echo "Warning: an old version of Go ($GO_VER) is installed on your system, ObjectBox needs >= 1.12" 66 | read -n 1 -s -r -p "Would you like to download Go $GO_VER_NEW? (y/n) " inp 67 | if [[ "$inp" == "y" ]]; then 68 | fetch_go 69 | else 70 | echo "Exiting, as ObjectBox cannot be used with Go $GO_VER" 71 | if [[ "$GO_DIR_MISSING" == "1" ]]; then 72 | rm -rf ~/go 73 | fi 74 | fi 75 | fi 76 | fi 77 | 78 | # get the ObjectBox binary library 79 | cd objectbox 80 | cat >update-objectbox.sh <<'EOL' 81 | #!/bin/bash 82 | set -eu 83 | cd "$(dirname "$0")" 84 | bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-go/main/install.sh) 85 | EOL 86 | chmod +x update-objectbox.sh 87 | ./update-objectbox.sh 88 | 89 | # create the demo project from examples/tasks 90 | exampleDir=~/projects/objectbox-go-test/ 91 | if [[ -d "${exampleDir}" ]]; then 92 | echo "Example directory ${exampleDir} already exists, skipping" 93 | else 94 | echo "Creating an example in ${exampleDir}" 95 | go get -d github.com/objectbox/objectbox-go/examples/tasks 96 | mkdir -p ${exampleDir} 97 | cd ${exampleDir} 98 | go mod init objectbox-go-test 99 | cp -r $GOPATH/src/github.com/objectbox/objectbox-go/examples/tasks/* ./ 100 | sed -i 's github.com/objectbox/objectbox-go/examples/tasks objectbox-go-test g' main.go 101 | go generate ./... 102 | go build 103 | echo "Launching the example program" 104 | ./objectbox-go-test 105 | fi 106 | -------------------------------------------------------------------------------- /objectbox/converters.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | import ( 20 | "fmt" 21 | "strconv" 22 | "time" 23 | ) 24 | 25 | // StringIdConvertToEntityProperty implements "StringIdConvert" property value converter 26 | func StringIdConvertToEntityProperty(dbValue uint64) (string, error) { 27 | return strconv.FormatUint(dbValue, 10), nil 28 | } 29 | 30 | // StringIdConvertToDatabaseValue implements "StringIdConvert" property value converter 31 | func StringIdConvertToDatabaseValue(goValue string) (uint64, error) { 32 | // in case the object was initialized by the user without setting the ID explicitly 33 | if goValue == "" { 34 | return 0, nil 35 | } 36 | return strconv.ParseUint(goValue, 10, 64) 37 | } 38 | 39 | // TimeInt64ConvertToEntityProperty converts Unix timestamp in milliseconds (ObjectBox date field) to time.Time 40 | // NOTE - you lose precision - anything smaller then milliseconds is dropped 41 | func TimeInt64ConvertToEntityProperty(dbValue int64) (time.Time, error) { 42 | return time.Unix(dbValue/1000, dbValue%1000*1000000).UTC(), nil 43 | } 44 | 45 | // TimeInt64ConvertToDatabaseValue converts time.Time to Unix timestamp in milliseconds (internal format expected by ObjectBox on a date field) 46 | // NOTE - you lose precision - anything smaller then milliseconds is dropped 47 | func TimeInt64ConvertToDatabaseValue(goValue time.Time) (int64, error) { 48 | var ms = int64(goValue.Nanosecond()) / 1000000 49 | return goValue.Unix()*1000 + ms, nil 50 | } 51 | 52 | // NanoTimeInt64ConvertToEntityProperty converts Unix timestamp in nanoseconds (ObjectBox date-nano field) to time.Time 53 | func NanoTimeInt64ConvertToEntityProperty(dbValue int64) (time.Time, error) { 54 | const nsInSec = 1000 * 1000 * 1000 55 | var seconds = dbValue / nsInSec 56 | return time.Unix(seconds, dbValue-seconds*nsInSec).UTC(), nil 57 | } 58 | 59 | // NanoTimeInt64ConvertToDatabaseValue converts time.Time to Unix timestamp in nanoseconds (internal format expected by ObjectBox on a date-nano field) 60 | func NanoTimeInt64ConvertToDatabaseValue(goValue time.Time) (int64, error) { 61 | return goValue.UnixNano(), nil 62 | } 63 | 64 | // TimeTextConvertToEntityProperty uses time.Time.UnmarshalText() to decode RFC 3339 formatted string to time.Time. 65 | func TimeTextConvertToEntityProperty(dbValue string) (goValue time.Time, err error) { 66 | err = goValue.UnmarshalText([]byte(dbValue)) 67 | if err != nil { 68 | err = fmt.Errorf("error unmarshalling time %v: %v", dbValue, err) 69 | } 70 | return goValue, err 71 | } 72 | 73 | // TimeTextConvertToDatabaseValue uses time.Time.MarshalText() to encode time.Time into RFC 3339 formatted string. 74 | func TimeTextConvertToDatabaseValue(goValue time.Time) (string, error) { 75 | bytes, err := goValue.MarshalText() 76 | if err != nil { 77 | err = fmt.Errorf("error marshalling time %v: %v", goValue, err) 78 | } 79 | return string(bytes), err 80 | } 81 | 82 | // TimeBinaryConvertToEntityProperty uses time.Time.UnmarshalBinary() to decode time.Time. 83 | func TimeBinaryConvertToEntityProperty(dbValue []byte) (goValue time.Time, err error) { 84 | err = goValue.UnmarshalBinary(dbValue) 85 | if err != nil { 86 | err = fmt.Errorf("error unmarshalling time %v: %v", dbValue, err) 87 | } 88 | return goValue, err 89 | } 90 | 91 | // TimeBinaryConvertToDatabaseValue uses time.Time.MarshalBinary() to encode time.Time. 92 | func TimeBinaryConvertToDatabaseValue(goValue time.Time) ([]byte, error) { 93 | bytes, err := goValue.MarshalBinary() 94 | if err != nil { 95 | err = fmt.Errorf("error marshalling time %v: %v", goValue, err) 96 | } 97 | return bytes, err 98 | } 99 | -------------------------------------------------------------------------------- /examples/tasks/README.md: -------------------------------------------------------------------------------- 1 | # Example: Tasks 2 | This example shows how to use the ObjectBox's GO API to create a simple console-base task-list application. 3 | 4 | To start the program just build & run the main.go, which launches an interactive console application 5 | 6 | ``` 7 | Welcome to the ObjectBox tasks-list app example 8 | Available commands are: 9 | ls [-a] list tasks - unfinished or all (-a flag) 10 | new Task text create a new task with the text 'Task text' 11 | done ID mark task with the given ID as done 12 | exit close the program 13 | help display this help 14 | $ 15 | ``` 16 | 17 | To create a new task, you can just type `new Buy milk` and hit enter. 18 | 19 | Now you can display the list of open tasks by running `ls` 20 | ```bash 21 | ID Created Finished Text 22 | 1 2018-11-14 14:36:57 +0100 CET Buy milk 23 | ``` 24 | 25 | To complete a task, you would execute command `done 1` (1 is the ID of the task as shown above). 26 | And to show all tasks, including the finished ones, you can run `ls -a`. 27 | 28 | ### Code overview 29 | Files: 30 | 31 | * [main.go](main.go): actual example code 32 | * [internal/model/task.go](internal/model/task.go): Task struct declaration 33 | * Other files in the [internal/model directory](internal/model) are generated by objectbox-gogen bindings generator utility. 34 | 35 | #### Code organization 36 | 37 | 1. The `main()` sets up ObjectBox store and auto-generated `TasksBox` which simplifies working with Tasks inside ObjectBox 38 | 1. `initObjectBox()` is the method that actually does ObjectBox setup based on the model. Note the unique IDs for the lastEntity 39 | and the call to `RegisterBinding` - they make sure the database schema is up-to-date with the code. 40 | 1. The `runInteractiveShell()` is just a command executor and doesn't contain any ObjectBox related code. 41 | It displays the shell and blocks until an `exit` command is entered. 42 | 1. After the exit command, execution of the program is back in the `main()` and objectbox is closed properly. 43 | Note that the `box` is closed before `ob` (ObjectBox) by the design of GO defer statement (last-in-first-out). 44 | 45 | The following methods demonstrate some of the API capabilities: 46 | 47 | * `printList()` - iterates over the whole dataset (calls `box.GetAll()`) and displays tasks in a table 48 | * `createTask()` - insert (calls `box.Put()`) 49 | * `setDone()` - select `box.Get()` & update `box.Put()` 50 | 51 | ### Changing the data model 52 | When the model is changed, you need to run `go generate` inside the model folder so that the generated bindings are updated. 53 | For convenience, the auto-generated files `task.obx.go` and `objectbox-model.json` are already generated for this example. 54 | 55 | You may want to change the data model to expend the example or to start your own data model for a new app. 56 | Here's what you have to do: 57 | 58 | 1. Edit `task.go` 59 | 2. Regenerate bindings and update model-info using [go generate](https://blog.golang.org/generate). 60 | For the `go generate` command to work, you need to have objectbox-gogen, a command line utility we have built for this purpose, installed and recognized in `$PATH`. 61 | 3. Once the bindings are changed, you might need to adjust your `initObjectBox()` if you have added an entity - register bindings for completely new entities and update ID/UID 62 | 63 | Some quick notes on IDs and UIDs in the ObjectBox model: 64 | 65 | * Entity IDs need to be unique among themselves 66 | * Property IDs need to be unique inside their Entity 67 | * All UIDs need to be unique application-wide 68 | * See [Meta Model, IDs, and UIDs](https://docs.objectbox.io/advanced/meta-model-ids-and-uids) docs for more details. 69 | 70 | ### License 71 | Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 72 | 73 | Licensed under the Apache License, Version 2.0 (the "License"); 74 | you may not use this file except in compliance with the License. 75 | You may obtain a copy of the License at 76 | 77 | http://www.apache.org/licenses/LICENSE-2.0 78 | 79 | Unless required by applicable law or agreed to in writing, software 80 | distributed under the License is distributed on an "AS IS" BASIS, 81 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 82 | See the License for the specific language governing permissions and 83 | limitations under the License. 84 | -------------------------------------------------------------------------------- /test/async_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "github.com/objectbox/objectbox-go/objectbox" 21 | "github.com/objectbox/objectbox-go/test/model" 22 | "testing" 23 | 24 | "github.com/objectbox/objectbox-go/test/assert" 25 | ) 26 | 27 | // TestBoxAsync tests the implicit AsyncBox returned by Box.Async() 28 | func TestBoxAsync(t *testing.T) { 29 | testAsync(t, func(box *model.TestEntityInlineBox) *model.TestEntityInlineAsyncBox { 30 | return box.Async() 31 | }) 32 | } 33 | 34 | const timeoutMs = 100 35 | 36 | // TestAsyncBox tests manually managed AsyncBox with custom timeout 37 | func TestAsyncBox(t *testing.T) { 38 | testAsync(t, func(box *model.TestEntityInlineBox) *model.TestEntityInlineAsyncBox { 39 | asyncBox, err := objectbox.NewAsyncBox(box.ObjectBox, model.TestEntityInlineBinding.Id, timeoutMs) 40 | assert.NoErr(t, err) 41 | assert.True(t, asyncBox != nil) 42 | return &model.TestEntityInlineAsyncBox{AsyncBox: asyncBox} 43 | }) 44 | } 45 | 46 | // TestAsyncBoxGenerated tests manually managed AsyncBox with custom timeout 47 | func TestAsyncBoxGenerated(t *testing.T) { 48 | testAsync(t, func(box *model.TestEntityInlineBox) *model.TestEntityInlineAsyncBox { 49 | asyncBox := model.AsyncBoxForTestEntityInline(box.ObjectBox, timeoutMs) 50 | assert.True(t, asyncBox != nil) 51 | return asyncBox 52 | }) 53 | } 54 | 55 | // testAsync tests all AsyncBox operations 56 | func testAsync(t *testing.T, asyncF func(box *model.TestEntityInlineBox) *model.TestEntityInlineAsyncBox) { 57 | var env = model.NewTestEnv(t) 58 | defer env.Close() 59 | 60 | var ob = env.ObjectBox 61 | var box = model.BoxForTestEntityInline(ob) 62 | var async = asyncF(box) 63 | defer func() { 64 | assert.NoErr(t, async.Close()) 65 | assert.NoErr(t, async.Close()) // test double close 66 | }() 67 | 68 | var waitAndCount = func(expected uint64) { 69 | assert.NoErr(t, async.AwaitSubmitted()) 70 | count, err := box.Count() 71 | assert.NoErr(t, err) 72 | assert.Eq(t, expected, count) 73 | } 74 | 75 | var object = &model.TestEntityInline{BaseWithValue: &model.BaseWithValue{}} 76 | id, err := async.Put(object) 77 | assert.NoErr(t, err) 78 | assert.Eq(t, id, object.Id) 79 | waitAndCount(1) 80 | 81 | // check the inserted object 82 | objectRead, err := box.Get(id) 83 | assert.NoErr(t, err) 84 | assert.Eq(t, *object, *objectRead) 85 | 86 | // update the object 87 | object.Value = object.Value + 1 88 | assert.NoErr(t, async.Update(object)) 89 | waitAndCount(1) 90 | 91 | // check the updated object 92 | objectRead, err = box.Get(id) 93 | assert.NoErr(t, err) 94 | assert.Eq(t, *object, *objectRead) 95 | 96 | err = async.Remove(object) 97 | assert.NoErr(t, err) 98 | waitAndCount(0) 99 | 100 | // while the update will ultimately fail because the object is removed, it can't know it in advance 101 | assert.NoErr(t, async.Update(object)) 102 | waitAndCount(0) 103 | 104 | // insert with a custom ID will work just fine 105 | id, err = async.Insert(object) 106 | assert.NoErr(t, err) 107 | assert.Eq(t, object.Id, id) 108 | 109 | id, err = async.Insert(&model.TestEntityInline{BaseWithValue: &model.BaseWithValue{}}) 110 | assert.NoErr(t, err) 111 | assert.Eq(t, object.Id+1, id) 112 | waitAndCount(2) 113 | 114 | objects, err := box.GetAll() 115 | assert.NoErr(t, err) 116 | assert.Eq(t, 2, len(objects)) 117 | assert.EqItems(t, []uint64{object.Id, id}, []uint64{objects[0].Id, objects[1].Id}) 118 | 119 | // insert with an existing ID will fail now (silently) 120 | var idBefore = object.Id 121 | _, err = async.Insert(object) 122 | assert.NoErr(t, err) 123 | assert.Eq(t, object.Id, idBefore) 124 | waitAndCount(2) 125 | 126 | assert.NoErr(t, async.RemoveId(object.Id)) 127 | waitAndCount(1) 128 | } 129 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | cLibVersion=4.2.0 5 | os=$(uname) 6 | cLibArgs="$*" 7 | 8 | # verify installed Go version 9 | goVersion=$(go version | cut -d' ' -f 3) 10 | goVersionMajor=$(echo "${goVersion}" | cut -d'.' -f1) 11 | goVersionMinor=$(echo "${goVersion}" | cut -d'.' -f2) 12 | goVersionPatch=$(echo "${goVersion}" | cut -d'.' -f3) 13 | if [[ ! "${goVersionMajor}" == "go1" ]]; then 14 | echo "Unexpected Go major version ${goVersionMajor}, expecting Go 1.11.4+." 15 | echo "You can proceed and let us know if you think we should extend the support to your version." 16 | elif [[ "${goVersionMinor}" -lt "11" ]]; then 17 | echo "Invalid Go version ${goVersion}, at least 1.11.4 required." 18 | exit 1 19 | elif [[ "${goVersionMinor}" == "11" ]] && [[ "${goVersionPatch}" -lt "4" ]]; then 20 | echo "Invalid Go version ${goVersion}, at least 1.11.4 required." 21 | exit 1 22 | fi 23 | 24 | # sudo might not be defined (e.g. when building a docker image) 25 | sudo="sudo " 26 | if [[ ! -x "$(command -v sudo)" ]]; then 27 | sudo="" 28 | fi 29 | 30 | # check a C/C++ compiler is available - it's used by CGO 31 | if [[ "$os" != MINGW* ]] && [[ "$os" != CYGWIN* ]]; then 32 | if [[ -z "${CC:-}" ]] && [[ -z "$(command -v gcc)" ]] && [[ -z "$(command -v clang)" ]]; then 33 | echo "Could not find a C/C++ compiler - \$CC environment variable is empty and neither gcc nor clang commands are recognized." 34 | echo "The compiler is required for Go CGO, please install gcc or clang, e.g. using your package manager." 35 | manager= 36 | installCmd1= 37 | installCmd2= 38 | if [[ -x "$(command -v apt)" ]]; then 39 | manager="APT" 40 | installCmd1="${sudo}apt update" 41 | installCmd2="${sudo}apt install gcc" 42 | elif [[ -x "$(command -v yum)" ]]; then 43 | manager="Yum" 44 | installCmd1="${sudo}yum install gcc" 45 | fi 46 | 47 | installed= 48 | if [[ -n "${manager}" ]]; then 49 | echo "Seems like your package manager is ${manager}, you may want to try: ${installCmd1} ; ${installCmd2}" 50 | read -p "Would you like to execute that command(s) now? [y/N] " -r 51 | if [[ $REPLY =~ ^[Yy]$ ]]; then 52 | ${installCmd1} 53 | ${installCmd2} 54 | if [[ -n "${CC:-}" ]] || [[ -n "$(command -v gcc)" ]] || [[ -n "$(command -v clang)" ]]; then 55 | installed=1 56 | fi 57 | fi 58 | fi 59 | 60 | if [[ -z "${installed}" ]]; then 61 | echo "Please restart this script after installing the compiler manually." 62 | read -p "If you think this is a false finding and would like to continue with ObjectBox installation regardless, press Y. [y/N] " -r 63 | if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit; fi 64 | fi 65 | fi 66 | fi 67 | 68 | # if there's no tty this is probably part of a docker build - therefore we install the c-api explicitly 69 | if [[ "$os" != MINGW* ]] && [[ "$os" != CYGWIN* ]] && [[ "$cLibArgs" != *"--install"* ]]; then 70 | tty -s || cLibArgs="${cLibArgs} --install" 71 | fi 72 | 73 | # install the ObjectBox-C library 74 | mkdir -p objectboxlib && cd objectboxlib 75 | bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-c/main/download.sh) ${cLibArgs} ${cLibVersion} 76 | if [[ "$os" == MINGW* ]] || [[ "$os" == CYGWIN* ]]; then 77 | localLibDir=$(realpath lib) 78 | echo "Windows must be able to find the objectbox.dll when executing ObjectBox based programs (even tests)." 79 | echo "One way to accomplish that is to manually copy the objectbox.dll from ${localLibDir} to C:/Windows/System32/ folder." 80 | echo "See https://golang.objectbox.io/install#objectbox-library-on-windows for more details." 81 | fi 82 | cd - 83 | 84 | if [[ -x "$(command -v ldconfig)" ]]; then 85 | libInfo=$(ldconfig -p | grep "libobjectbox." || true) 86 | if [ -z "${libInfo}" ]; then 87 | echo "Installation of the C library failed - ldconfig -p doesn't report libobjectbox. Please try running again." 88 | exit 1 89 | fi 90 | fi 91 | 92 | # go get flatbuffers (if not using go modules) and objectbox 93 | if [[ ! -f "go.mod" ]]; then 94 | echo "Your project doesn't seem to be using go modules. Installing FlatBuffers & ObjectBox using go get." 95 | go get -u github.com/google/flatbuffers/go 96 | go get -u github.com/objectbox/objectbox-go/objectbox 97 | 98 | elif grep -q "module github.com/objectbox/objectbox-go" "go.mod"; then 99 | echo "Seems like we're running inside the objectbox-go directory itself, skipping ObjectBox Go module installation." 100 | 101 | else 102 | go get -u github.com/objectbox/objectbox-go/objectbox 103 | fi 104 | 105 | echo "Installation complete." 106 | echo "You can start using ObjectBox by importing github.com/objectbox/objectbox-go/objectbox in your source code." -------------------------------------------------------------------------------- /test/version_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "regexp" 21 | "strings" 22 | "testing" 23 | 24 | "github.com/objectbox/objectbox-go/objectbox" 25 | "github.com/objectbox/objectbox-go/test/assert" 26 | ) 27 | 28 | func TestObjectBoxVersionString(t *testing.T) { 29 | versionInfo := objectbox.VersionInfo() 30 | t.Log(versionInfo) 31 | 32 | var format = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?$`) 33 | 34 | versionGo := objectbox.VersionGo() 35 | versionGoString := versionGo.String() 36 | if !format.MatchString(versionGoString) { 37 | t.Errorf("ObjectBox-Go version %v doesn't match expected regexp %v", versionGoString, format) 38 | } 39 | 40 | versionLib := objectbox.VersionLib() 41 | versionLibString := versionLib.String() 42 | if !format.MatchString(versionGoString) { 43 | t.Errorf("ObjectBox-C version %v doesn't match expected regexp %v", versionLibString, format) 44 | } 45 | 46 | assert.Eq(t, true, strings.Contains(versionInfo, versionGoString)) 47 | assert.Eq(t, true, strings.Contains(versionInfo, versionLibString)) 48 | } 49 | 50 | func TestExpectedObjectBoxVersion(t *testing.T) { 51 | versionGo := objectbox.VersionGo() 52 | versionGoInt := versionGo.Major*10000 + versionGo.Minor*100 + versionGo.Patch 53 | assert.True(t, versionGoInt >= 10900) // Update with new releases (won't fail if forgotten); e.g. >= V2.0.0 54 | assert.True(t, versionGoInt < 30000) // Future next major release; e.g. not yet V3.0.0 55 | 56 | versionLib := objectbox.VersionLib() 57 | versionLibInt := versionLib.Major*10000 + versionLib.Minor*100 + versionLib.Patch 58 | assert.True(t, versionLibInt >= 40100) // Update with new releases (won't fail if forgotten); e.g. >= V4.1.0 59 | assert.True(t, versionLibInt < 50000) // Future next major release; e.g. not yet V5.0.0 60 | } 61 | 62 | func TestObjectBoxMinLibVersion(t *testing.T) { 63 | assert.True(t, objectbox.VersionLib().GreaterThanOrEqualTo(objectbox.VersionLibMin())) 64 | assert.True(t, objectbox.VersionLibMinRecommended().GreaterThanOrEqualTo(objectbox.VersionLibMin())) 65 | assert.True(t, objectbox.VersionLibStatic().GreaterThanOrEqualTo(objectbox.VersionLibMinRecommended())) 66 | } 67 | 68 | func TestVersionAgainstZeros(t *testing.T) { 69 | zeros := objectbox.Version{Major: 0, Minor: 0, Patch: 0} 70 | 71 | assert.True(t, zeros.LessThan(objectbox.VersionLibMin())) 72 | assert.True(t, zeros.LessThan(objectbox.VersionLibMinRecommended())) 73 | assert.True(t, zeros.LessThan(objectbox.VersionLib())) 74 | assert.True(t, zeros.LessThan(objectbox.VersionGo())) 75 | 76 | assert.True(t, objectbox.VersionLibMin().GreaterThanOrEqualTo(zeros)) 77 | assert.True(t, objectbox.VersionLibMinRecommended().GreaterThanOrEqualTo(zeros)) 78 | assert.True(t, objectbox.VersionLib().GreaterThanOrEqualTo(zeros)) 79 | assert.True(t, objectbox.VersionGo().GreaterThanOrEqualTo(zeros)) 80 | } 81 | 82 | func TestVersion(t *testing.T) { 83 | assert.True(t, objectbox.Version{Major: 0, Minor: 0, Patch: 0}.LessThan(objectbox.Version{Major: 0, Minor: 0, Patch: 1})) 84 | assert.True(t, objectbox.Version{Major: 0, Minor: 0, Patch: 1}.LessThan(objectbox.Version{Major: 0, Minor: 1, Patch: 0})) 85 | assert.True(t, objectbox.Version{Major: 0, Minor: 1, Patch: 1}.LessThan(objectbox.Version{Major: 1, Minor: 0, Patch: 0})) 86 | assert.True(t, objectbox.Version{Major: 0, Minor: 1, Patch: 0}.LessThan(objectbox.Version{Major: 0, Minor: 1, Patch: 1})) 87 | assert.True(t, objectbox.Version{Major: 1, Minor: 1, Patch: 0}.LessThan(objectbox.Version{Major: 1, Minor: 1, Patch: 1})) 88 | assert.True(t, objectbox.Version{Major: 1, Minor: 0, Patch: 1}.LessThan(objectbox.Version{Major: 1, Minor: 1, Patch: 1})) 89 | assert.True(t, objectbox.Version{Major: 0, Minor: 21, Patch: 1}.LessThan(objectbox.Version{Major: 4, Minor: 1, Patch: 0})) 90 | assert.True(t, objectbox.Version{Major: 0, Minor: 18, Patch: 1}.LessThan(objectbox.Version{Major: 0, Minor: 19, Patch: 0})) 91 | } 92 | 93 | func TestVersionLabel(t *testing.T) { 94 | var version = objectbox.Version{Major: 1, Minor: 2, Patch: 3, Label: "beta"} 95 | assert.Eq(t, version.String(), "1.2.3-beta") 96 | version.Label = "" 97 | assert.Eq(t, version.String(), "1.2.3") 98 | } 99 | -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | # remove the space in the following to require running this script as administrator 2 | # Requires -RunAsAdministrator 3 | 4 | # terminate on uncaught exceptions 5 | $ErrorActionPreference = "Stop" 6 | 7 | # Configure supported HTTPS protocols 8 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls 9 | 10 | $libVersion = '4.2.0' 11 | $libVariant = 'objectbox' # or 'objectbox-sync' 12 | $downloadDir = 'download' 13 | $extractedLibDir = "$downloadDir\objectbox-$libVersion" 14 | 15 | function DownloadLibrary { 16 | # Windows hashes and URL sourced from objectbox-c download.sh 17 | $machine = if ([Environment]::Is64BitOperatingSystem) { 'x64' } else { 'x86' } 18 | $url = "https://github.com/objectbox/objectbox-c/releases/download/v$libVersion/$libVariant-windows-$machine.zip" 19 | 20 | $archiveFile = "$downloadDir\objectbox-$libVersion.zip" 21 | 22 | if (!(Test-Path "$downloadDir" -PathType Container)) { 23 | Write-Host "Creating download directory: '$downloadDir'" 24 | New-Item "$downloadDir" -ItemType directory 25 | } 26 | 27 | Write-Host "Downloading C-API v$libVersion into $archiveFile" 28 | Write-Host "Downloading from URL: $url" 29 | 30 | $wc = New-Object System.Net.WebClient 31 | $wc.DownloadFile($url, $archiveFile) 32 | 33 | if (!(Test-Path "$extractedLibDir" -PathType Container)) { 34 | New-Item "$extractedLibDir" -ItemType directory 35 | } 36 | 37 | Write-Host "Extracting into $extractedLibDir" 38 | Expand-Archive "$archiveFile" "$extractedLibDir" -Force 39 | } 40 | 41 | function ValidateInstallation { 42 | param ($sourceFile, $targetFile) 43 | 44 | if ((Get-Item $sourceFile).Length -eq (Get-Item $targetFile).Length) { 45 | Write-Host "Successfully installed $targetFile" -ForegroundColor Green 46 | } else { 47 | throw "Installation to $targetFile failed - source and target contents don't match" 48 | } 49 | } 50 | 51 | function InstallWithPrompt { 52 | param ($libDir) 53 | 54 | $reply = Read-Host -Prompt "Would you like to install ObjectBox library into $($libDir)? [y/N]" 55 | if (!($reply -match "[yY]")) { 56 | Write-Host "OK, skipping installation to $libDir" 57 | return 58 | } 59 | 60 | $sourceFile = "$extractedLibDir\lib\objectbox.dll" 61 | $targetFile = "$libDir\objectbox.dll" 62 | 63 | Write-Host "Copying $sourceFile to $libDir" 64 | try { 65 | Copy-Item $sourceFile $libDir 66 | ValidateInstallation $sourceFile $targetFile 67 | return 68 | } catch [System.UnauthorizedAccessException] { 69 | Write-Host "Can't copy: $($_.Exception.Message)" -ForegroundColor Yellow 70 | } 71 | 72 | # reaches here only when copying fails because of UnauthorizedAccessException 73 | $reply = Read-Host -Prompt "Would you like to retry as administrator? [y/N]" 74 | if (!($reply -match "[yY]")) { 75 | Write-Host "OK, skipping installation to $libDir" 76 | return 77 | } 78 | 79 | $sourceFile = "$pwd\$sourceFile" # sub-shell requires an absolute path. -WorkingDirectory argument doesn't work either. 80 | $expectedSize = (Get-Item $sourceFile).Length 81 | $verifyCmd = "if ((Get-Item $targetFile).Length -ne $expectedSize) {Write-Host 'Installation failed.'; Read-Host -Prompt 'Press any key to exit this window'}" 82 | $cmd = "Copy-Item $sourceFile $libDir ; $verifyCmd" 83 | Start-Process powershell.exe -Verb runas -ArgumentList $cmd 84 | 85 | ValidateInstallation $sourceFile $targetFile 86 | } 87 | 88 | function InstallIntoGCC { 89 | Write-Host "Determining path to your local GCC installation - necessary for compiling your programs with ObjectBox" 90 | 91 | $libDir = "." 92 | try { 93 | # try to find gcc 94 | $gcc = Get-Command gcc 95 | Write-Host "Found GCC: $($gcc.Path)" 96 | $libDir = Split-Path $gcc.Path -Parent 97 | $libDir = Split-Path $libDir -Parent 98 | $libDir = Join-Path $libDir "lib" 99 | } 100 | catch { 101 | Write-Host "GCC installation not found, skipping" 102 | return 103 | } 104 | 105 | InstallWithPrompt $libDir 106 | } 107 | 108 | function InstallIntoSys32 { 109 | Write-Host "Windows needs to find your library during runtime (incl. tests execution)." 110 | Write-Host "The simplest way to achieve this is to install the library globally." 111 | InstallWithPrompt "C:\Windows\System32" 112 | } 113 | 114 | try { 115 | DownloadLibrary 116 | Write-Host "" 117 | 118 | InstallIntoGCC 119 | Write-Host "" 120 | 121 | InstallIntoSys32 122 | Write-Host "" 123 | 124 | Write-Host "Installation complete" -ForegroundColor Green 125 | } catch { 126 | Write-Error $error[0].Exception -ErrorAction Continue 127 | } 128 | 129 | Read-Host -Prompt 'Press any key to exit' -------------------------------------------------------------------------------- /test/performance/perf.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "github.com/objectbox/objectbox-go/objectbox" 22 | "github.com/objectbox/objectbox-go/test/performance/perf" 23 | "path/filepath" 24 | "runtime" 25 | "time" 26 | ) 27 | 28 | type executor struct { 29 | ob *objectbox.ObjectBox 30 | box *perf.EntityBox 31 | times map[string][]time.Duration // arrays of runtimes indexed by function name 32 | } 33 | 34 | func createExecutor(dbName string) *executor { 35 | result := &executor{ 36 | times: map[string][]time.Duration{}, 37 | } 38 | result.initObjectBox(dbName) 39 | return result 40 | } 41 | 42 | func (exec *executor) initObjectBox(dbName string) { 43 | defer exec.trackTime(time.Now()) 44 | 45 | objectBox, err := objectbox.NewBuilder().Directory(dbName).Model(perf.ObjectBoxModel()).Build() 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | exec.ob = objectBox 51 | exec.box = perf.BoxForEntity(objectBox) 52 | } 53 | 54 | func (exec *executor) close() { 55 | defer exec.trackTime(time.Now()) 56 | 57 | exec.ob.Close() 58 | } 59 | 60 | func (exec *executor) trackTime(start time.Time) { 61 | elapsed := time.Since(start) 62 | 63 | pc, _, _, _ := runtime.Caller(1) 64 | fun := filepath.Ext(runtime.FuncForPC(pc).Name())[1:] 65 | exec.times[fun] = append(exec.times[fun], elapsed) 66 | } 67 | 68 | func (exec *executor) printTimes(functions []string) { 69 | // print the whole data as a table 70 | fmt.Println("Function\tRuns\tAverage ms\tAll times") 71 | 72 | if len(functions) == 0 { 73 | for fun := range exec.times { 74 | functions = append(functions, fun) 75 | } 76 | } 77 | 78 | for _, fun := range functions { 79 | times := exec.times[fun] 80 | 81 | sum := int64(0) 82 | for _, duration := range times { 83 | sum += duration.Nanoseconds() 84 | } 85 | fmt.Printf("%s\t%d\t%f", fun, len(times), float64(sum/int64(len(times)))/1000000) 86 | 87 | for _, duration := range times { 88 | fmt.Printf("\t%f", float64(duration.Nanoseconds())/1000000) 89 | } 90 | fmt.Println() 91 | } 92 | } 93 | 94 | func (exec *executor) removeAll() { 95 | defer exec.trackTime(time.Now()) 96 | err := exec.box.RemoveAll() 97 | if err != nil { 98 | panic(err) 99 | } 100 | } 101 | 102 | func (exec *executor) prepareData(count int) []*perf.Entity { 103 | defer exec.trackTime(time.Now()) 104 | 105 | var result = make([]*perf.Entity, count) 106 | for i := 0; i < count; i++ { 107 | result[i] = &perf.Entity{ 108 | String: fmt.Sprintf("Entity no. %d", i), 109 | Float64: float64(i), 110 | Int32: int32(i), 111 | Int64: int64(i), 112 | } 113 | } 114 | 115 | return result 116 | } 117 | 118 | func (exec *executor) putAsync(items []*perf.Entity) { 119 | defer exec.trackTime(time.Now()) 120 | 121 | const retries = 20 122 | 123 | var putErr error 124 | for _, item := range items { 125 | for i := 0; i < retries; i++ { 126 | if _, putErr = exec.box.PutAsync(item); putErr != nil { 127 | // before each retry we sleep for a little more 128 | time.Sleep(time.Duration(i+1) * time.Second) 129 | } else { 130 | break 131 | } 132 | } 133 | 134 | // if retrying failed, stop completely 135 | if putErr != nil { 136 | break 137 | } 138 | } 139 | 140 | if err := exec.ob.AwaitAsyncCompletion(); err != nil { 141 | panic(err) 142 | } 143 | 144 | // if retrying failed 145 | if putErr != nil { 146 | panic(putErr) 147 | } 148 | } 149 | 150 | func (exec *executor) putMany(items []*perf.Entity) { 151 | defer exec.trackTime(time.Now()) 152 | 153 | if _, err := exec.box.PutMany(items); err != nil { 154 | panic(err) 155 | } 156 | } 157 | 158 | func (exec *executor) readAll(count int) []*perf.Entity { 159 | defer exec.trackTime(time.Now()) 160 | 161 | if items, err := exec.box.GetAll(); err != nil { 162 | panic(err) 163 | } else if len(items) != count { 164 | panic("invalid number of objects read") 165 | } else { 166 | return items 167 | } 168 | } 169 | 170 | func (exec *executor) changeValues(items []*perf.Entity) { 171 | defer exec.trackTime(time.Now()) 172 | 173 | count := len(items) 174 | for i := 0; i < count; i++ { 175 | items[i].Int64 = items[i].Int64 * 2 176 | } 177 | } 178 | 179 | func (exec *executor) updateAll(items []*perf.Entity) { 180 | defer exec.trackTime(time.Now()) 181 | 182 | if _, err := exec.box.PutMany(items); err != nil { 183 | panic(err) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /test/converters_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "github.com/objectbox/objectbox-go/objectbox" 21 | "testing" 22 | "time" 23 | 24 | "github.com/objectbox/objectbox-go/test/assert" 25 | "github.com/objectbox/objectbox-go/test/model" 26 | ) 27 | 28 | func TestTimeConverter(t *testing.T) { 29 | var env = model.NewTestEnv(t) 30 | defer env.Close() 31 | 32 | date, err := time.Parse(time.RFC3339, "2018-11-28T12:16:42.145+07:00") 33 | assert.NoErr(t, err) 34 | 35 | id := env.PutEntity(&model.Entity{Date: date}) 36 | assert.Eq(t, uint64(1), id) 37 | 38 | read, err := env.Box.Get(id) 39 | assert.NoErr(t, err) 40 | assert.Eq(t, date.UnixNano(), read.Date.UnixNano()) 41 | } 42 | 43 | func TestComplex128Converter(t *testing.T) { 44 | var env = model.NewTestEnv(t) 45 | defer env.Close() 46 | 47 | var value = complex(14, 125) 48 | 49 | id := env.PutEntity(&model.Entity{Complex128: value}) 50 | assert.Eq(t, uint64(1), id) 51 | 52 | read, err := env.Box.Get(id) 53 | assert.NoErr(t, err) 54 | assert.Eq(t, value, read.Complex128) 55 | } 56 | 57 | func TestStringIdConverter(t *testing.T) { 58 | { 59 | value, err := objectbox.StringIdConvertToEntityProperty(0) 60 | assert.NoErr(t, err) 61 | assert.Eq(t, "0", value) 62 | } 63 | 64 | { 65 | value, err := objectbox.StringIdConvertToDatabaseValue("0") 66 | assert.NoErr(t, err) 67 | assert.Eq(t, uint64(0), value) 68 | } 69 | 70 | { 71 | value, err := objectbox.StringIdConvertToEntityProperty(10) 72 | assert.NoErr(t, err) 73 | assert.Eq(t, "10", value) 74 | } 75 | 76 | { 77 | value, err := objectbox.StringIdConvertToDatabaseValue("10") 78 | assert.NoErr(t, err) 79 | assert.Eq(t, uint64(10), value) 80 | } 81 | 82 | { 83 | value, err := objectbox.StringIdConvertToDatabaseValue("invalid") 84 | assert.Err(t, err) 85 | assert.Eq(t, uint64(0), value) 86 | } 87 | } 88 | 89 | func TestTimeInt64Converter(t *testing.T) { 90 | var test = func(expected string, timestamp int64) { 91 | value, err := objectbox.TimeInt64ConvertToEntityProperty(timestamp) 92 | assert.NoErr(t, err) 93 | assert.Eq(t, expected, value.String()) 94 | } 95 | 96 | test("1970-01-01 00:00:00 +0000 UTC", 0) 97 | test("1970-01-01 00:00:01.234 +0000 UTC", 1234) 98 | test("1969-12-31 23:59:54.322 +0000 UTC", -5678) 99 | 100 | { 101 | var date = time.Now() 102 | value, err := objectbox.TimeInt64ConvertToDatabaseValue(date) 103 | assert.NoErr(t, err) 104 | assert.Eq(t, date.UnixNano()/1000000, value) 105 | } 106 | } 107 | 108 | func TestNanoTimeInt64Converter(t *testing.T) { 109 | var test = func(expected string, timestamp int64) { 110 | value, err := objectbox.NanoTimeInt64ConvertToEntityProperty(timestamp) 111 | assert.NoErr(t, err) 112 | assert.Eq(t, expected, value.String()) 113 | } 114 | 115 | test("1970-01-01 00:00:00 +0000 UTC", 0) 116 | test("1970-01-01 00:00:00.000001234 +0000 UTC", 1234) 117 | test("1969-12-31 23:59:59.999994322 +0000 UTC", -5678) 118 | 119 | { 120 | var date = time.Now().UTC() 121 | value, err := objectbox.NanoTimeInt64ConvertToDatabaseValue(date) 122 | assert.NoErr(t, err) 123 | assert.Eq(t, date.UnixNano(), value) 124 | date2, err := objectbox.NanoTimeInt64ConvertToEntityProperty(value) 125 | assert.Eq(t, date, date2) 126 | } 127 | } 128 | 129 | func TestTimeTextConverter(t *testing.T) { 130 | date := time.Unix(time.Now().Unix(), int64(time.Now().Nanosecond())) // get date without monotonic clock reading 131 | bytes, err := date.MarshalText() 132 | assert.NoErr(t, err) 133 | 134 | { 135 | value, err := objectbox.TimeTextConvertToDatabaseValue(date) 136 | assert.NoErr(t, err) 137 | assert.Eq(t, string(bytes), value) 138 | } 139 | 140 | { 141 | value, err := objectbox.TimeTextConvertToEntityProperty(string(bytes)) 142 | assert.NoErr(t, err) 143 | assert.Eq(t, date.UnixNano(), value.UnixNano()) 144 | } 145 | } 146 | 147 | func TestTimeBinaryConverter(t *testing.T) { 148 | date := time.Unix(time.Now().Unix(), int64(time.Now().Nanosecond())) // get date without monotonic clock reading 149 | bytes, err := date.MarshalBinary() 150 | assert.NoErr(t, err) 151 | 152 | { 153 | value, err := objectbox.TimeBinaryConvertToDatabaseValue(date) 154 | assert.NoErr(t, err) 155 | assert.Eq(t, bytes, value) 156 | } 157 | 158 | { 159 | value, err := objectbox.TimeBinaryConvertToEntityProperty(bytes) 160 | assert.NoErr(t, err) 161 | assert.Eq(t, date, value) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /test/sync_server_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "bytes" 21 | "net" 22 | "net/url" 23 | "os" 24 | "os/exec" 25 | "strconv" 26 | "strings" 27 | "testing" 28 | "time" 29 | 30 | "github.com/objectbox/objectbox-go/test/assert" 31 | "github.com/objectbox/objectbox-go/test/model" 32 | ) 33 | 34 | // find a free (available) port to bind to 35 | func findFreeTCPPort(t *testing.T) int { 36 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 37 | assert.NoErr(t, err) 38 | 39 | listener, err := net.ListenTCP("tcp", addr) 40 | assert.NoErr(t, err) 41 | var port = listener.Addr().(*net.TCPAddr).Port 42 | listener.Close() 43 | return port 44 | } 45 | 46 | // testSyncServer wraps a sync-server binary and executes it. 47 | // The binary must be present in the test folder or a known executable (available in $PATH). 48 | type testSyncServer struct { 49 | t *testing.T 50 | err error 51 | cmd *exec.Cmd 52 | env *model.TestEnv 53 | port int 54 | } 55 | 56 | func NewTestSyncServer(t *testing.T) *testSyncServer { 57 | var execPath = findSyncServerExecutable(t) 58 | 59 | var server = &testSyncServer{ 60 | t: t, 61 | port: findFreeTCPPort(t), 62 | } 63 | 64 | // will be executed in case of error in this function 65 | var cleanup = server.Close 66 | defer func() { 67 | if cleanup != nil { 68 | cleanup() 69 | } 70 | }() 71 | 72 | // prepare a database directory 73 | server.env = model.NewTestEnv(server.t) 74 | server.env.ObjectBox.Close() // close the database so that the server can open it 75 | 76 | server.cmd = exec.Command(execPath, 77 | "--db-directory="+server.env.Directory, 78 | "--bind="+server.URI(), 79 | "--admin-bind=127.0.0.1:"+strconv.FormatInt(int64(findFreeTCPPort(t)), 10), 80 | ) 81 | server.cmd.Stdout = &bytes.Buffer{} 82 | server.cmd.Stderr = &bytes.Buffer{} 83 | 84 | // start the server 85 | assert.NoErr(t, server.cmd.Start()) 86 | 87 | // wait for the server to start listening for connections 88 | uri, err := url.Parse(server.URI()) 89 | assert.NoErr(t, err) 90 | assert.NoErr(t, waitUntil(5*time.Second, func() (b bool, e error) { 91 | conn, err := net.DialTimeout("tcp", uri.Hostname()+":"+uri.Port(), 5*time.Second) 92 | 93 | // if connection was successful, stop waiting (return true) 94 | if err == nil { 95 | return true, conn.Close() 96 | } 97 | 98 | // if the connection was refused, try again next time 99 | if strings.Contains(err.Error(), "connection refused") { 100 | return false, nil 101 | } 102 | 103 | // fail immediately on other errors 104 | return false, err 105 | })) 106 | 107 | cleanup = nil // no error, don't close the server 108 | return server 109 | } 110 | 111 | func findSyncServerExecutable(t *testing.T) string { 112 | // sync-server executable must be located in the `test` directory (CWD when executing tests) or available in $PATH 113 | const executable = "sync-server" 114 | var path = "./" + executable 115 | 116 | // check if the executable exists in the CWD - that one will have preference 117 | if _, err := os.Stat(executable); err != nil { 118 | if !os.IsNotExist(err) { 119 | assert.NoErr(t, err) 120 | } 121 | 122 | // if not found in CWD, try to look up in $PATH 123 | path, err = exec.LookPath(executable) 124 | if err != nil { 125 | cwd, err := os.Getwd() 126 | assert.NoErr(t, err) 127 | 128 | t.Skipf("%s executable not found in %v and is not available in $PATH either", executable, cwd) 129 | } 130 | } 131 | return path 132 | } 133 | 134 | func (server *testSyncServer) Close() { 135 | if server.env == nil { 136 | return 137 | } 138 | 139 | defer func() { 140 | server.env.Close() 141 | server.env = nil 142 | server.cmd = nil 143 | }() 144 | 145 | // wait for the server to finish 146 | assert.NotNil(server.t, server.cmd.Process) 147 | assert.NoErr(server.t, server.cmd.Process.Signal(os.Interrupt)) 148 | var err = server.cmd.Wait() 149 | 150 | // print the output 151 | server.t.Log("sync-server output: \n" + server.cmd.Stdout.(*bytes.Buffer).String()) 152 | 153 | if server.cmd.Stderr.(*bytes.Buffer).Len() > 0 { 154 | server.t.Log("sync-server errors: \n" + server.cmd.Stderr.(*bytes.Buffer).String()) 155 | server.t.Fail() 156 | } 157 | 158 | assert.NoErr(server.t, err) 159 | } 160 | 161 | func (server *testSyncServer) URI() string { 162 | return "ws://127.0.0.1:" + strconv.FormatInt(int64(server.port), 10) 163 | } 164 | 165 | func TestSyncServer(t *testing.T) { 166 | var server = NewTestSyncServer(t) 167 | server.Close() 168 | } 169 | -------------------------------------------------------------------------------- /test/concurrency_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "runtime" 21 | "strings" 22 | "sync" 23 | "testing" 24 | 25 | "github.com/objectbox/objectbox-go/test/assert" 26 | "github.com/objectbox/objectbox-go/test/model/iot" 27 | ) 28 | 29 | func TestConcurrentPut(t *testing.T) { 30 | if testing.Short() { 31 | concurrentInsert(t, 50, 10, false) 32 | } else { 33 | concurrentInsert(t, 100, 20, false) 34 | } 35 | } 36 | 37 | func TestConcurrentPutAsync(t *testing.T) { 38 | count := 100000 39 | 40 | if testing.Short() || strings.Contains(strings.ToLower(runtime.GOARCH), "arm") { 41 | count = 10000 42 | } 43 | 44 | concurrentInsert(t, count, 20, true) 45 | } 46 | 47 | func concurrentInsert(t *testing.T, count, concurrency int, putAsync bool) { 48 | env := iot.NewTestEnv() 49 | defer env.Close() 50 | box := iot.BoxForEvent(env.ObjectBox) 51 | 52 | err := box.RemoveAll() 53 | assert.NoErr(t, err) 54 | 55 | var countPart = count / concurrency 56 | assert.Eq(t, 0, count%concurrency) 57 | 58 | // prepare channels and launch the goroutines 59 | ids := make(chan uint64, count) 60 | errors := make(chan error, count) 61 | 62 | t.Logf("launching %d routines to insert %d objects each", concurrency, countPart) 63 | 64 | var wg sync.WaitGroup 65 | wg.Add(concurrency) 66 | for i := concurrency; i > 0; i-- { 67 | go func() { 68 | defer wg.Done() 69 | for i := countPart; i > 0; i-- { 70 | var id uint64 71 | var e error 72 | 73 | event := iot.Event{ 74 | Device: "my device", 75 | } 76 | 77 | if putAsync { 78 | id, e = box.PutAsync(&event) 79 | } else { 80 | id, e = box.Put(&event) 81 | } 82 | 83 | if e != nil { 84 | errors <- e 85 | } else { 86 | ids <- id 87 | } 88 | } 89 | }() 90 | } 91 | 92 | // collect and check results after everything is done 93 | t.Log("waiting for all goroutines to finish") 94 | wg.Wait() 95 | 96 | assert.NoErr(t, env.ObjectBox.AwaitAsyncCompletion()) 97 | 98 | if len(errors) != 0 { 99 | t.Errorf("encountered %d errors:", len(errors)) 100 | for i := 0; i < len(errors); i++ { 101 | t.Log(" ", <-errors) 102 | } 103 | } 104 | 105 | t.Log("validating counts") 106 | assert.Eq(t, 0, len(errors)) 107 | assert.Eq(t, count, len(ids)) 108 | 109 | actualCount, err := box.Count() 110 | assert.NoErr(t, err) 111 | assert.Eq(t, uint64(count), actualCount) 112 | 113 | // check whether the IDs are unique 114 | t.Log("validating IDs") 115 | idsMap := make(map[uint64]bool) 116 | for i := count; i > 0; i-- { 117 | id := <-ids 118 | if idsMap[id] != false { 119 | assert.Failf(t, "duplicate ID %d", id) 120 | } else { 121 | idsMap[id] = true 122 | } 123 | } 124 | } 125 | 126 | // TestConcurrentQuery checks concurrently running queries. 127 | // Previously there was an issue with finalizers, with query being closed during the native call. 128 | func TestConcurrentQuery(t *testing.T) { 129 | env := iot.NewTestEnv() 130 | defer env.Close() 131 | 132 | box := iot.BoxForEvent(env.ObjectBox) 133 | 134 | err := box.RemoveAll() 135 | assert.NoErr(t, err) 136 | 137 | var objects = 10000 138 | var queries = 500 139 | var concurrency = 4 140 | 141 | if testing.Short() || strings.Contains(strings.ToLower(runtime.GOARCH), "arm") { 142 | objects = 5000 143 | queries = 200 144 | concurrency = 2 145 | } 146 | 147 | assert.NoErr(t, env.ObjectBox.RunInWriteTx(func() error { 148 | for i := objects; i > 0; i-- { 149 | if _, e := box.Put(&iot.Event{ 150 | Device: "my device", 151 | }); e != nil { 152 | return e 153 | } 154 | } 155 | return nil 156 | })) 157 | 158 | // prepare channels and launch the goroutines 159 | errors := make(chan error, queries) 160 | 161 | t.Logf("launching %d routines to execute %d queries each, over %d objects", concurrency, queries, objects) 162 | 163 | var wg sync.WaitGroup 164 | wg.Add(concurrency) 165 | for i := concurrency; i > 0; i-- { 166 | go func() { 167 | defer wg.Done() 168 | for j := queries; j > 0; j-- { 169 | var e error 170 | if j%2 == 0 { 171 | _, e = box.Query(iot.Event_.Id.GreaterThan(0)).Find() 172 | } else { 173 | _, e = box.Query(iot.Event_.Id.GreaterThan(0)).FindIds() 174 | } 175 | if e != nil { 176 | errors <- e 177 | break 178 | } 179 | } 180 | }() 181 | } 182 | 183 | // collect and check results after everything is done 184 | t.Log("waiting for all goroutines to finish") 185 | wg.Wait() 186 | 187 | assert.NoErr(t, env.ObjectBox.AwaitAsyncCompletion()) 188 | 189 | if len(errors) != 0 { 190 | t.Errorf("encountered %d errors:", len(errors)) 191 | for i := 0; i < len(errors); i++ { 192 | t.Log(" ", <-errors) 193 | } 194 | } 195 | assert.Eq(t, 0, len(errors)) 196 | } 197 | -------------------------------------------------------------------------------- /examples/tasks/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2022 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "bufio" 21 | "fmt" 22 | "os" 23 | "strconv" 24 | "strings" 25 | "time" 26 | 27 | "github.com/objectbox/objectbox-go/examples/tasks/internal/model" 28 | "github.com/objectbox/objectbox-go/objectbox" 29 | ) 30 | 31 | func main() { 32 | objectBox := initObjectBox() 33 | defer objectBox.Close() 34 | 35 | box := model.BoxForTask(objectBox) 36 | 37 | checkStartSyncClient(objectBox, box) 38 | 39 | runInteractiveShell(box) 40 | } 41 | 42 | func runInteractiveShell(box *model.TaskBox) { 43 | // our simple interactive shell 44 | reader := bufio.NewReader(os.Stdin) 45 | 46 | fmt.Println("Welcome to the ObjectBox tasks-list app example") 47 | printHelp() 48 | 49 | for { 50 | fmt.Print("$ ") 51 | input, err := reader.ReadString('\n') 52 | if err != nil { 53 | fmt.Fprintln(os.Stderr, err) 54 | } 55 | 56 | //input = strings.TrimSuffix(input, "\n") 57 | input = strings.TrimSpace(input) 58 | args := strings.Fields(input) 59 | 60 | switch strings.ToLower(args[0]) { 61 | case "new": 62 | createTask(box, strings.Join(args[1:], " ")) 63 | case "done": 64 | if len(args) != 2 { 65 | fmt.Fprintf(os.Stderr, "wrong number of arguments, expecting exactly one\n") 66 | } else if id, err := strconv.ParseUint(args[1], 10, 64); err != nil { 67 | fmt.Fprintf(os.Stderr, "could not parse ID: %s\n", err) 68 | } else { 69 | setDone(box, id) 70 | } 71 | case "ls": 72 | if len(args) < 2 { 73 | printList(box, false) 74 | } else if args[1] == "-a" { 75 | printList(box, true) 76 | } else { 77 | fmt.Fprintf(os.Stderr, "unknown argument %s\n", args[1]) 78 | fmt.Println() 79 | } 80 | case "exit": 81 | return 82 | case "help": 83 | printHelp() 84 | default: 85 | fmt.Fprintf(os.Stderr, "unknown command %s\n", input) 86 | printHelp() 87 | } 88 | } 89 | } 90 | 91 | func initObjectBox() *objectbox.ObjectBox { 92 | objectBox, err := objectbox.NewBuilder().Model(model.ObjectBoxModel()).Build() 93 | if err != nil { 94 | panic(err) 95 | } 96 | return objectBox 97 | } 98 | 99 | func printHelp() { 100 | fmt.Println("Available commands are: ") 101 | fmt.Println(" ls [-a] list tasks - unfinished or all (-a flag)") 102 | fmt.Println(" new Task text create a new task with the text 'Task text'") 103 | fmt.Println(" done ID mark task with the given ID as done") 104 | fmt.Println(" exit close the program") 105 | fmt.Println(" help display this help") 106 | } 107 | 108 | func createTask(box *model.TaskBox, text string) { 109 | task := &model.Task{ 110 | Text: text, 111 | DateCreated: time.Now(), 112 | DateFinished: time.Unix(0, 0), // use "epoch start" to unify values across platforms (e.g for Sync) 113 | } 114 | 115 | if id, err := box.Put(task); err != nil { 116 | fmt.Fprintf(os.Stderr, "could not create task: %s\n", err) 117 | } else { 118 | task.Id = id 119 | fmt.Printf("task ID %d successfully created\n", task.Id) 120 | } 121 | } 122 | 123 | func printList(box *model.TaskBox, all bool) { 124 | var list []*model.Task 125 | var err error 126 | 127 | if all { // load all tasks 128 | list, err = box.GetAll() 129 | } else { // load only unfinished tasks (value 0 is "epoch start") 130 | list, err = box.Query(model.Task_.DateFinished.Equals(0)).Find() 131 | } 132 | 133 | if err != nil { 134 | fmt.Fprintf(os.Stderr, "could not list tasks: %s\n", err) 135 | } 136 | 137 | fmt.Printf("%3s %-23s %-23s %s\n", "ID", "Created", "Finished", "Text") 138 | for _, task := range list { 139 | fmt.Printf("%3d %-23s %-23s %s\n", 140 | task.Id, task.DateCreated.Format("2006-01-02 15:04:05"), task.DateFinished.Format("2006-01-02 15:04:05"), task.Text) 141 | } 142 | } 143 | 144 | func setDone(box *model.TaskBox, id uint64) { 145 | if task, err := box.Get(id); err != nil { 146 | fmt.Fprintf(os.Stderr, "could not read task ID %d: %s\n", id, err) 147 | } else if task == nil { 148 | fmt.Fprintf(os.Stderr, "task ID %d doesn't exist\n", id) 149 | } else { 150 | task.DateFinished = time.Now() 151 | if _, err := box.Put(task); err != nil { 152 | fmt.Fprintf(os.Stderr, "could not update task ID %d: %s\n", id, err) 153 | } else { 154 | fmt.Printf("task ID %d completed at %s\n", id, task.DateFinished.String()) 155 | } 156 | } 157 | } 158 | 159 | func checkStartSyncClient(ob *objectbox.ObjectBox, box *model.TaskBox) { // only if sync-enabled library is used 160 | 161 | if objectbox.SyncIsAvailable() { 162 | syncClient, err := objectbox.NewSyncClient( 163 | ob, 164 | "ws://127.0.0.1", // wss for SSL, ws for unencrypted traffic 165 | objectbox.SyncCredentialsNone()) 166 | 167 | if err == nil { 168 | syncClient.Start() // Connect and start syncing. 169 | fmt.Println("Sync client started.") 170 | syncClient.SetChangeListener(func(changes []*objectbox.SyncChange) { 171 | 172 | fmt.Printf("received %d changes\n", len(changes)) 173 | printList(box, true) 174 | }) 175 | } else { 176 | fmt.Println("Could not start the sync client.") 177 | } 178 | } else { 179 | fmt.Println("Sync is not available. Please go to https://sync.objectbox.io/ for more information.") 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /objectbox/builder.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2022 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | /* 20 | #include <stdlib.h> 21 | #include "objectbox.h" 22 | */ 23 | import "C" 24 | 25 | import ( 26 | "fmt" 27 | "runtime" 28 | "unsafe" 29 | ) 30 | 31 | // Builder provides tools to fully configure and construct ObjectBox 32 | type Builder struct { 33 | model *Model 34 | Error error 35 | 36 | // these options are used when creating the underlying store using the C-api calls 37 | // pointers are used to distinguish whether a value is present or not 38 | directory *string 39 | maxSizeInKb *uint64 40 | maxReaders *uint 41 | 42 | // these options are passed-through to the created ObjectBox struct 43 | options 44 | } 45 | 46 | // NewBuilder creates a new ObjectBox instance builder object 47 | func NewBuilder() *Builder { 48 | var version = VersionLib() 49 | if version.LessThan(VersionLibMin()) { 50 | panic("The loaded ObjectBox C library is too old for this build of ObjectBox Go.\n" + 51 | "Found version " + version.String() + ", but at least " + VersionLibMin().String() + " is required.\n" + 52 | "Please see https://github.com/objectbox/objectbox-go on how to upgrade.\n" + 53 | "Or, check https://github.com/objectbox/objectbox-c for the C library.") 54 | } else if version.LessThan(VersionLibMinRecommended()) { 55 | println("Note: the loaded ObjectBox C library should be updated.\n" + 56 | " Found ObjectBox version " + version.String() + ", but the minimum recommended version is " + 57 | VersionLibMinRecommended().String() + ".") 58 | } 59 | 60 | return &Builder{ 61 | options: options{ 62 | // defaults 63 | asyncTimeout: 1000, // 1s ; TODO make this 0 to use core default? 64 | }, 65 | } 66 | } 67 | 68 | // Directory configures the path where the database is stored 69 | func (builder *Builder) Directory(path string) *Builder { 70 | builder.directory = &path 71 | return builder 72 | } 73 | 74 | // MaxSizeInKb defines maximum size the database can take on disk (default: 1 GByte). 75 | func (builder *Builder) MaxSizeInKb(maxSizeInKb uint64) *Builder { 76 | builder.maxSizeInKb = &maxSizeInKb 77 | return builder 78 | } 79 | 80 | // MaxReaders defines maximum concurrent readers (default: 126). 81 | // Increase only if you are getting errors (highly concurrent scenarios). 82 | func (builder *Builder) MaxReaders(maxReaders uint) *Builder { 83 | builder.maxReaders = &maxReaders 84 | return builder 85 | } 86 | 87 | // asyncTimeoutTBD configures the default enqueue timeout for async operations (default is 1 second). 88 | // See Box.PutAsync method doc for more information. 89 | // TODO: implement this option in core and use it 90 | func (builder *Builder) asyncTimeoutTBD(milliseconds uint) *Builder { 91 | builder.asyncTimeout = milliseconds 92 | return builder 93 | } 94 | 95 | // Model specifies schema for the database. 96 | // 97 | // Pass the result of the generated function ObjectBoxModel as an argument: Model(ObjectBoxModel()) 98 | func (builder *Builder) Model(model *Model) *Builder { 99 | if builder.Error != nil { 100 | return builder 101 | } 102 | 103 | builder.Error = model.validate() 104 | if builder.Error != nil { 105 | builder.model = nil 106 | } else { 107 | builder.model = model 108 | } 109 | 110 | return builder 111 | } 112 | 113 | // Build validates the configuration and tries to init the ObjectBox. 114 | // This call panics on failures; if ObjectBox is optional for your app, consider BuildOrError(). 115 | func (builder *Builder) Build() (*ObjectBox, error) { 116 | objectBox, err := builder.BuildOrError() 117 | if err != nil { 118 | panic(fmt.Sprintf("Could not create ObjectBox - please check configuration: %s", err)) 119 | } 120 | return objectBox, nil 121 | } 122 | 123 | // BuildOrError validates the configuration and tries to init the ObjectBox. 124 | func (builder *Builder) BuildOrError() (*ObjectBox, error) { 125 | if builder.Error != nil { 126 | return nil, builder.Error 127 | } 128 | 129 | if builder.model == nil { 130 | return nil, fmt.Errorf("model is not defined") 131 | } 132 | 133 | // for native calls/createError() 134 | runtime.LockOSThread() 135 | defer runtime.UnlockOSThread() 136 | 137 | cOptions := C.obx_opt() 138 | if cOptions == nil { 139 | return nil, createError() 140 | } 141 | 142 | if builder.directory != nil { 143 | cDir := C.CString(*builder.directory) 144 | defer C.free(unsafe.Pointer(cDir)) 145 | if 0 != C.obx_opt_directory(cOptions, cDir) { 146 | C.obx_opt_free(cOptions) 147 | return nil, createError() 148 | } 149 | } 150 | 151 | if builder.maxSizeInKb != nil { 152 | C.obx_opt_max_db_size_in_kb(cOptions, C.uint64_t(*builder.maxSizeInKb)) 153 | } 154 | 155 | if builder.maxReaders != nil { 156 | C.obx_opt_max_readers(cOptions, C.uint(*builder.maxReaders)) 157 | } 158 | 159 | C.obx_opt_model(cOptions, builder.model.cModel) 160 | 161 | // cOptions is consumed by obx_store_open() so no need to free it 162 | cStore := C.obx_store_open(cOptions) 163 | if cStore == nil { 164 | return nil, createError() 165 | } 166 | 167 | ob := &ObjectBox{ 168 | store: cStore, 169 | entitiesById: builder.model.entitiesById, 170 | entitiesByName: builder.model.entitiesByName, 171 | boxes: make(map[TypeId]*Box, len(builder.model.entitiesById)), 172 | options: builder.options, 173 | } 174 | 175 | for _, entity := range builder.model.entitiesById { 176 | entity.objectBox = ob 177 | } 178 | return ob, nil 179 | } 180 | -------------------------------------------------------------------------------- /test/nil_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/objectbox/objectbox-go/test/assert" 23 | "github.com/objectbox/objectbox-go/test/model" 24 | ) 25 | 26 | func TestNilPropertiesWhenNil(t *testing.T) { 27 | env := model.NewTestEnv(t) 28 | defer env.Close() 29 | 30 | box := model.BoxForEntity(env.ObjectBox) 31 | 32 | var object = &model.Entity{} 33 | id, err := box.Put(object) 34 | assert.NoErr(t, err) 35 | assert.Eq(t, uint64(1), id) 36 | 37 | object, err = box.Get(id) 38 | assert.NoErr(t, err) 39 | assert.Eq(t, id, object.Id) 40 | assert.True(t, nil == object.IntPtr) 41 | assert.True(t, nil == object.Int8Ptr) 42 | assert.True(t, nil == object.Int16Ptr) 43 | assert.True(t, nil == object.Int32Ptr) 44 | assert.True(t, nil == object.Int64Ptr) 45 | assert.True(t, nil == object.UintPtr) 46 | assert.True(t, nil == object.Uint8Ptr) 47 | assert.True(t, nil == object.Uint16Ptr) 48 | assert.True(t, nil == object.Uint32Ptr) 49 | assert.True(t, nil == object.Uint64Ptr) 50 | assert.True(t, nil == object.BoolPtr) 51 | assert.True(t, nil == object.StringPtr) 52 | assert.True(t, nil == object.StringVectorPtr) 53 | assert.True(t, nil == object.BytePtr) 54 | assert.True(t, nil == object.ByteVectorPtr) 55 | assert.True(t, nil == object.RunePtr) 56 | assert.True(t, nil == object.Float32Ptr) 57 | assert.True(t, nil == object.Float64Ptr) 58 | } 59 | 60 | func TestNilPropertiesWithValues(t *testing.T) { 61 | env := model.NewTestEnv(t) 62 | defer env.Close() 63 | 64 | box := model.BoxForEntity(env.ObjectBox) 65 | 66 | // source for the values 67 | var prototype = model.Entity47() 68 | 69 | var object = &model.Entity{} 70 | object.IntPtr = &prototype.Int 71 | object.Int8Ptr = &prototype.Int8 72 | object.Int16Ptr = &prototype.Int16 73 | object.Int32Ptr = &prototype.Int32 74 | object.Int64Ptr = &prototype.Int64 75 | object.UintPtr = &prototype.Uint 76 | object.Uint8Ptr = &prototype.Uint8 77 | object.Uint16Ptr = &prototype.Uint16 78 | object.Uint32Ptr = &prototype.Uint32 79 | object.Uint64Ptr = &prototype.Uint64 80 | object.BoolPtr = &prototype.Bool 81 | object.StringPtr = &prototype.String 82 | object.StringVectorPtr = &prototype.StringVector 83 | object.BytePtr = &prototype.Byte 84 | object.ByteVectorPtr = &prototype.ByteVector 85 | object.RunePtr = &prototype.Rune 86 | object.Float32Ptr = &prototype.Float32 87 | object.Float64Ptr = &prototype.Float64 88 | 89 | id, err := box.Put(object) 90 | assert.NoErr(t, err) 91 | assert.Eq(t, uint64(1), id) 92 | 93 | object, err = box.Get(id) 94 | assert.NoErr(t, err) 95 | assert.Eq(t, id, object.Id) 96 | 97 | assert.True(t, nil != object.IntPtr) 98 | assert.True(t, nil != object.Int8Ptr) 99 | assert.True(t, nil != object.Int16Ptr) 100 | assert.True(t, nil != object.Int32Ptr) 101 | assert.True(t, nil != object.Int64Ptr) 102 | assert.True(t, nil != object.UintPtr) 103 | assert.True(t, nil != object.Uint8Ptr) 104 | assert.True(t, nil != object.Uint16Ptr) 105 | assert.True(t, nil != object.Uint32Ptr) 106 | assert.True(t, nil != object.Uint64Ptr) 107 | assert.True(t, nil != object.BoolPtr) 108 | assert.True(t, nil != object.StringPtr) 109 | assert.True(t, nil != object.StringVectorPtr) 110 | assert.True(t, nil != object.BytePtr) 111 | assert.True(t, nil != object.ByteVectorPtr) 112 | assert.True(t, nil != object.RunePtr) 113 | assert.True(t, nil != object.Float32Ptr) 114 | assert.True(t, nil != object.Float64Ptr) 115 | 116 | assert.Eq(t, prototype.Int, *object.IntPtr) 117 | assert.Eq(t, prototype.Int8, *object.Int8Ptr) 118 | assert.Eq(t, prototype.Int16, *object.Int16Ptr) 119 | assert.Eq(t, prototype.Int32, *object.Int32Ptr) 120 | assert.Eq(t, prototype.Int64, *object.Int64Ptr) 121 | assert.Eq(t, prototype.Uint, *object.UintPtr) 122 | assert.Eq(t, prototype.Uint8, *object.Uint8Ptr) 123 | assert.Eq(t, prototype.Uint16, *object.Uint16Ptr) 124 | assert.Eq(t, prototype.Uint32, *object.Uint32Ptr) 125 | assert.Eq(t, prototype.Uint64, *object.Uint64Ptr) 126 | assert.Eq(t, prototype.Bool, *object.BoolPtr) 127 | assert.Eq(t, prototype.String, *object.StringPtr) 128 | assert.Eq(t, prototype.StringVector, *object.StringVectorPtr) 129 | assert.Eq(t, prototype.Byte, *object.BytePtr) 130 | assert.Eq(t, prototype.ByteVector, *object.ByteVectorPtr) 131 | assert.Eq(t, prototype.Rune, *object.RunePtr) 132 | assert.Eq(t, prototype.Float32, *object.Float32Ptr) 133 | assert.Eq(t, prototype.Float64, *object.Float64Ptr) 134 | } 135 | 136 | // This tests correct behaviour of both vectors that are nil and those that are empty 137 | func TestNilPropertiesVectors(t *testing.T) { 138 | env := model.NewTestEnv(t) 139 | defer env.Close() 140 | 141 | box := model.BoxForEntity(env.ObjectBox) 142 | 143 | // empty vectors (not nil!) 144 | id, err := box.Put(&model.Entity{ 145 | StringVector: []string{}, 146 | StringVectorPtr: &[]string{}, 147 | ByteVector: []byte{}, 148 | ByteVectorPtr: &[]byte{}, 149 | }) 150 | assert.NoErr(t, err) 151 | 152 | object, err := box.Get(id) 153 | assert.NoErr(t, err) 154 | assert.True(t, nil != object.StringVector) 155 | assert.True(t, nil != object.StringVectorPtr) 156 | assert.True(t, nil != object.ByteVector) 157 | assert.True(t, nil != object.ByteVectorPtr) 158 | 159 | // nil vectors 160 | id, err = box.Put(&model.Entity{ 161 | StringVector: nil, 162 | StringVectorPtr: nil, 163 | ByteVector: nil, 164 | ByteVectorPtr: nil, 165 | }) 166 | assert.NoErr(t, err) 167 | 168 | object, err = box.Get(id) 169 | assert.NoErr(t, err) 170 | assert.True(t, nil == object.StringVector) 171 | assert.True(t, nil == object.StringVectorPtr) 172 | assert.True(t, nil == object.ByteVector) 173 | assert.True(t, nil == object.ByteVectorPtr) 174 | } 175 | -------------------------------------------------------------------------------- /objectbox/relation.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | import ( 20 | "fmt" 21 | ) 22 | 23 | type conditionRelationOneToMany struct { 24 | relation *RelationToOne 25 | conditions []Condition 26 | alias *string // this is only used to report an error 27 | } 28 | 29 | func (condition *conditionRelationOneToMany) applyTo(qb *QueryBuilder, isRoot bool) (ConditionId, error) { 30 | if condition.alias != nil { 31 | return 0, fmt.Errorf("using Alias/As(\"%s\") on a OneToMany relation link is not supported", *condition.alias) 32 | } 33 | 34 | return conditionIdFakeLink, qb.LinkOneToMany(condition.relation, condition.conditions) 35 | } 36 | 37 | // Alias sets a string alias for the given condition. It can later be used in Query.Set*Params() methods. 38 | // This is an invalid call on Relation links and will result in an error. 39 | func (condition *conditionRelationOneToMany) Alias(alias string) Condition { 40 | condition.alias = &alias 41 | return condition 42 | } 43 | 44 | // As sets an alias for the given condition. It can later be used in Query.Set*Params() methods. 45 | // This is an invalid call on Relation links and will result in an error. 46 | func (condition *conditionRelationOneToMany) As(alias *alias) Condition { 47 | condition.alias = alias.alias() 48 | return condition 49 | } 50 | 51 | // RelationToOne holds information about a relation link on a property. 52 | // It is used in generated entity code, providing a way to create a query across multiple related entities. 53 | // Internally, the property value holds an ID of an object in the target entity. 54 | type RelationToOne struct { 55 | Property *BaseProperty 56 | Target *Entity 57 | } 58 | 59 | func (relation RelationToOne) entityId() TypeId { 60 | return relation.Property.Entity.Id 61 | } 62 | 63 | func (relation RelationToOne) propertyId() TypeId { 64 | return relation.Property.Id 65 | } 66 | 67 | func (relation RelationToOne) alias() *string { 68 | return nil 69 | } 70 | 71 | // Link creates a connection and takes inner conditions to evaluate on the linked entity. 72 | func (relation *RelationToOne) Link(conditions ...Condition) Condition { 73 | return &conditionRelationOneToMany{relation: relation, conditions: conditions} 74 | } 75 | 76 | // Equals finds entities with relation target ID equal to the given value 77 | func (relation RelationToOne) Equals(value uint64) Condition { 78 | return &conditionClosure{ 79 | apply: func(qb *QueryBuilder) (ConditionId, error) { 80 | return qb.IntEqual(relation.Property, int64(value)) 81 | }, 82 | } 83 | } 84 | 85 | // NotEquals finds entities with relation target ID different than the given value 86 | func (relation RelationToOne) NotEquals(value uint64) Condition { 87 | return &conditionClosure{ 88 | apply: func(qb *QueryBuilder) (ConditionId, error) { 89 | return qb.IntNotEqual(relation.Property, int64(value)) 90 | }, 91 | } 92 | } 93 | 94 | func (relation RelationToOne) int64Slice(values []uint64) []int64 { 95 | result := make([]int64, len(values)) 96 | 97 | for i, v := range values { 98 | result[i] = int64(v) 99 | } 100 | 101 | return result 102 | } 103 | 104 | // In finds entities with relation target ID equal to any of the given values 105 | func (relation RelationToOne) In(values ...uint64) Condition { 106 | return &conditionClosure{ 107 | apply: func(qb *QueryBuilder) (ConditionId, error) { 108 | return qb.Int64In(relation.Property, relation.int64Slice(values)) 109 | }, 110 | } 111 | } 112 | 113 | // NotIn finds entities with relation target ID not equal to any the given values 114 | func (relation RelationToOne) NotIn(values ...uint64) Condition { 115 | return &conditionClosure{ 116 | apply: func(qb *QueryBuilder) (ConditionId, error) { 117 | return qb.Int64NotIn(relation.Property, relation.int64Slice(values)) 118 | }, 119 | } 120 | } 121 | 122 | type conditionRelationManyToMany struct { 123 | relation *RelationToMany 124 | conditions []Condition 125 | alias *string // this is only used to report an error 126 | } 127 | 128 | func (condition *conditionRelationManyToMany) applyTo(qb *QueryBuilder, isRoot bool) (ConditionId, error) { 129 | if condition.alias != nil { 130 | return 0, fmt.Errorf("using Alias/As(\"%s\") on a ManyToMany relation link is not supported", *condition.alias) 131 | } 132 | 133 | return conditionIdFakeLink, qb.LinkManyToMany(condition.relation, condition.conditions) 134 | } 135 | 136 | // Alias sets a string alias for the given condition. It can later be used in Query.Set*Params() methods. 137 | // This is an invalid call on Relation links and will result in an error. 138 | func (condition *conditionRelationManyToMany) Alias(alias string) Condition { 139 | condition.alias = &alias 140 | return condition 141 | } 142 | 143 | // As sets an alias for the given condition. It can later be used in Query.Set*Params() methods. 144 | // This is an invalid call on Relation links and will result in an error. 145 | func (condition *conditionRelationManyToMany) As(alias *alias) Condition { 146 | condition.alias = alias.alias() 147 | return condition 148 | } 149 | 150 | // RelationToMany holds information about a standalone relation link between two entities. 151 | // It is used in generated entity code, providing a way to create a query across multiple related entities. 152 | // Internally, the relation is stored separately, holding pairs of source & target object IDs. 153 | type RelationToMany struct { 154 | Id TypeId 155 | Source *Entity 156 | Target *Entity 157 | } 158 | 159 | // Link creates a connection and takes inner conditions to evaluate on the linked entity. 160 | func (relation *RelationToMany) Link(conditions ...Condition) Condition { 161 | return &conditionRelationManyToMany{relation: relation, conditions: conditions} 162 | } 163 | 164 | // TODO contains() would make sense for many-to-many (slice) 165 | -------------------------------------------------------------------------------- /test/bench_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | import ( 20 | "fmt" 21 | "github.com/objectbox/objectbox-go/objectbox" 22 | "github.com/objectbox/objectbox-go/test/performance/perf" 23 | "os" 24 | "testing" 25 | ) 26 | 27 | // Implements simple benchmarks as an alternative to the "test/performance". However, it doesn't achieve the optimal 28 | // performance as the standalone one so the following benchmarks are only for quick regression testing. 29 | 30 | // a function instead of a global variable to make sure testing.Short is initialized already 31 | func bulkCount() int { 32 | var bulkCount = 10000 33 | 34 | if testing.Short() { 35 | bulkCount = 100 36 | } 37 | 38 | return bulkCount 39 | } 40 | 41 | func prepareBenchData(b *testing.B, count int) []*perf.Entity { 42 | b.StopTimer() 43 | 44 | var result = make([]*perf.Entity, count) 45 | for i := 0; i < count; i++ { 46 | result[i] = &perf.Entity{ 47 | String: fmt.Sprintf("Entity no. %d", i), 48 | Float64: float64(i), 49 | Int32: int32(i), 50 | Int64: int64(i), 51 | } 52 | } 53 | 54 | b.StartTimer() 55 | return result 56 | } 57 | 58 | type benchmarkEnv struct { 59 | dbName string 60 | ob *objectbox.ObjectBox 61 | box *perf.EntityBox 62 | b *testing.B 63 | } 64 | 65 | func newBenchEnv(b *testing.B) *benchmarkEnv { 66 | b.StopTimer() 67 | b.SetBytes(1) // report speed in MB/s where one B is one object; overridden in bulk ops 68 | 69 | var env = &benchmarkEnv{ 70 | dbName: "testdata", 71 | b: b, 72 | } 73 | 74 | var err error 75 | env.ob, err = objectbox.NewBuilder().Directory(env.dbName).Model(perf.ObjectBoxModel()).Build() 76 | env.check(err) 77 | 78 | env.box = perf.BoxForEntity(env.ob) 79 | 80 | b.StartTimer() 81 | b.ReportAllocs() 82 | return env 83 | } 84 | 85 | func (env *benchmarkEnv) close() { 86 | env.b.StopTimer() 87 | env.ob.Close() 88 | os.RemoveAll(env.dbName) 89 | env.b.StartTimer() 90 | } 91 | 92 | func (env *benchmarkEnv) check(err error) { 93 | if err != nil { 94 | env.b.Error(err) 95 | } 96 | } 97 | 98 | func BenchmarkPutMany(b *testing.B) { 99 | var env = newBenchEnv(b) 100 | defer env.close() 101 | var inserts = prepareBenchData(b, bulkCount()) 102 | 103 | b.Run(fmt.Sprintf("count=%v", bulkCount()), func(b *testing.B) { 104 | b.SetBytes(int64(bulkCount())) // report speed in MB/s where one B is one object 105 | b.ReportAllocs() 106 | for n := 0; n < b.N; n++ { 107 | _, err := env.box.PutMany(inserts) 108 | env.check(err) 109 | 110 | b.StopTimer() 111 | env.box.RemoveAll() 112 | b.StartTimer() 113 | } 114 | }) 115 | } 116 | 117 | func BenchmarkGetAll(b *testing.B) { 118 | var env = newBenchEnv(b) 119 | defer env.close() 120 | var inserts = prepareBenchData(b, bulkCount()) 121 | 122 | b.StopTimer() 123 | _, err := env.box.PutMany(inserts) 124 | env.check(err) 125 | b.StartTimer() 126 | 127 | b.Run("GetAll", func(b *testing.B) { 128 | b.SetBytes(int64(bulkCount())) // report speed in MB/s where one B is one object 129 | b.ReportAllocs() 130 | for n := 0; n < b.N; n++ { 131 | objects, err := env.box.GetAll() 132 | if err != nil { 133 | b.Error(err) 134 | } else if len(objects) != bulkCount() { 135 | b.Errorf("invalid number of objects received: %v instead of %v", len(objects), bulkCount()) 136 | } 137 | } 138 | }) 139 | } 140 | 141 | func BenchmarkRemoveAll(b *testing.B) { 142 | var env = newBenchEnv(b) 143 | defer env.close() 144 | var inserts = prepareBenchData(b, bulkCount()) 145 | 146 | b.Run("count=%v", func(b *testing.B) { 147 | b.SetBytes(int64(bulkCount())) // report speed in MB/s where one B is one object 148 | b.ReportAllocs() 149 | for n := 0; n < b.N; n++ { 150 | b.StopTimer() 151 | _, err := env.box.PutMany(inserts) 152 | env.check(err) 153 | b.StartTimer() 154 | err = env.box.RemoveAll() 155 | env.check(err) 156 | } 157 | }) 158 | } 159 | 160 | // BenchmarkTxPut executes many individual puts in a single transaction. 161 | func BenchmarkTxPut(b *testing.B) { 162 | var env = newBenchEnv(b) 163 | defer env.close() 164 | 165 | // prepare the data first 166 | var inserts = prepareBenchData(b, b.N) 167 | 168 | // execute in a single transaction 169 | env.check(env.ob.RunInWriteTx(func() error { 170 | for n := 0; n < b.N; n++ { 171 | _, err := env.box.Put(inserts[n]) 172 | env.check(err) 173 | } 174 | return nil 175 | })) 176 | } 177 | 178 | // BenchmarkSlowPut executes many individual puts, each in its own transaction (internally). 179 | func BenchmarkSlowPut(b *testing.B) { 180 | var env = newBenchEnv(b) 181 | defer env.close() 182 | 183 | // prepare the data first 184 | var inserts = prepareBenchData(b, b.N) 185 | 186 | for n := 0; n < b.N; n++ { 187 | _, err := env.box.Put(inserts[n]) 188 | env.check(err) 189 | } 190 | } 191 | 192 | // BenchmarkTxGet reads a single object from DB many times, all in a single transaction 193 | func BenchmarkTxGet(b *testing.B) { 194 | var env = newBenchEnv(b) 195 | defer env.close() 196 | 197 | // prepare the data first 198 | var inserts = prepareBenchData(b, 1) 199 | b.StopTimer() 200 | _, err := env.box.Put(inserts[0]) 201 | env.check(err) 202 | b.StartTimer() 203 | 204 | env.check(env.ob.RunInReadTx(func() error { 205 | for n := 0; n < b.N; n++ { 206 | _, err := env.box.Get(inserts[0].ID) 207 | env.check(err) 208 | } 209 | return nil 210 | })) 211 | } 212 | 213 | // BenchmarkGet reads a single object from DB many times, each in its own transaction (internally) 214 | func BenchmarkSlowGet(b *testing.B) { 215 | var env = newBenchEnv(b) 216 | defer env.close() 217 | 218 | // prepare the data first 219 | var inserts = prepareBenchData(b, 1) 220 | b.StopTimer() 221 | _, err := env.box.Put(inserts[0]) 222 | env.check(err) 223 | b.StartTimer() 224 | 225 | for n := 0; n < b.N; n++ { 226 | _, err := env.box.Get(inserts[0].ID) 227 | env.check(err) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /test/model/testenv.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "os" 23 | "strings" 24 | "testing" 25 | 26 | "github.com/objectbox/objectbox-go/objectbox" 27 | "github.com/objectbox/objectbox-go/test/assert" 28 | ) 29 | 30 | // TestEnv provides environment for testing ObjectBox. It sets up the database and populates it with data. 31 | type TestEnv struct { 32 | ObjectBox *objectbox.ObjectBox 33 | Box *EntityBox 34 | Directory string 35 | 36 | t *testing.T 37 | options TestEnvOptions 38 | } 39 | 40 | // TestEnvOptions configure the TestEnv 41 | type TestEnvOptions struct { 42 | PopulateRelations bool 43 | } 44 | 45 | // NewTestEnv creates the test environment 46 | func NewTestEnv(t *testing.T) *TestEnv { 47 | // Test in a temporary directory - if tested by an end user, the repo is read-only. 48 | tempDir, err := ioutil.TempDir("", "objectbox-test") 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | var env = &TestEnv{ 54 | Directory: tempDir, 55 | t: t, 56 | } 57 | 58 | env.ObjectBox, err = objectbox.NewBuilder().Directory(env.Directory).Model(ObjectBoxModel()).Build() 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | env.Box = BoxForEntity(env.ObjectBox) 64 | 65 | return env 66 | } 67 | 68 | // Close closes ObjectBox and removes the database 69 | func (env *TestEnv) Close() { 70 | env.ObjectBox.Close() 71 | os.RemoveAll(env.Directory) 72 | } 73 | 74 | func (env *TestEnv) SyncClient(serverUri string) *objectbox.SyncClient { 75 | client, err := objectbox.NewSyncClient(env.ObjectBox, serverUri, objectbox.SyncCredentialsNone()) 76 | assert.NoErr(env.t, err) 77 | assert.True(env.t, client != nil) 78 | return client 79 | } 80 | 81 | func removeFileIfExists(path string) error { 82 | if _, err := os.Stat(path); err == nil { 83 | return os.Remove(path) 84 | } 85 | return nil 86 | } 87 | 88 | // SetOptions configures options 89 | func (env *TestEnv) SetOptions(options TestEnvOptions) *TestEnv { 90 | env.options = options 91 | return env 92 | } 93 | 94 | // Populate creates given number of entities in the database 95 | func (env *TestEnv) Populate(count uint) { 96 | // the first one is always the special Entity47 97 | env.PutEntity(entity47(1, &env.options)) 98 | 99 | if count > 1 { 100 | // additionally an entity with upper case String 101 | var e = entity47(1, &env.options) 102 | e.String = strings.ToUpper(e.String) 103 | env.PutEntity(e) 104 | } 105 | 106 | if count > 2 { 107 | var toInsert = count - 2 108 | 109 | // insert some data - different values but dependable 110 | var limit = float64(4294967295) // uint max so that when we multiply with 42, we get some large int64 values 111 | var step = limit / float64(toInsert) * 2 112 | var entities = make([]*Entity, toInsert) 113 | var i = uint(0) 114 | for coef := -limit; i < toInsert; coef += step { 115 | entities[i] = entity47(int64(coef), &env.options) 116 | i++ 117 | } 118 | 119 | _, err := env.Box.PutMany(entities) 120 | assert.NoErr(env.t, err) 121 | } 122 | 123 | c, err := env.Box.Count() 124 | assert.NoErr(env.t, err) 125 | assert.Eq(env.t, uint64(count), c) 126 | } 127 | 128 | // PutEntity creates an entity 129 | func (env *TestEnv) PutEntity(entity *Entity) uint64 { 130 | id, err := env.Box.Put(entity) 131 | assert.NoErr(env.t, err) 132 | 133 | return id 134 | } 135 | 136 | // Entity47 creates a test entity ("47" because int fields are multiples of 47) 137 | func Entity47() *Entity { 138 | return entity47(1, nil) 139 | } 140 | 141 | // entity47 creates a test entity ("47" because int fields are multiples of 47) 142 | func entity47(coef int64, options *TestEnvOptions) *Entity { 143 | // NOTE, it doesn't really matter that we overflow the smaller types 144 | var Bool = coef%2 == 1 145 | 146 | var String string 147 | if Bool { 148 | String = fmt.Sprintf("Val-%d", coef) 149 | } else { 150 | String = fmt.Sprintf("val-%d", coef) 151 | } 152 | 153 | var object = &Entity{ 154 | Int: int(int32(47 * coef)), 155 | Int8: 47 * int8(coef), 156 | Int16: 47 * int16(coef), 157 | Int32: 47 * int32(coef), 158 | Int64: 47 * int64(coef), 159 | Uint: uint(uint32(47 * coef)), 160 | Uint8: 47 * uint8(coef), 161 | Uint16: 47 * uint16(coef), 162 | Uint32: 47 * uint32(coef), 163 | Uint64: 47 * uint64(coef), 164 | Bool: Bool, 165 | String: String, 166 | StringVector: []string{fmt.Sprintf("first-%d", coef), fmt.Sprintf("second-%d", coef), ""}, 167 | Byte: 47 * byte(coef), 168 | ByteVector: []byte{1 * byte(coef), 2 * byte(coef), 3 * byte(coef), 5 * byte(coef), 8 * byte(coef)}, 169 | Rune: 47 * rune(coef), 170 | Float32: 47.74 * float32(coef), 171 | Float64: 47.74 * float64(coef), 172 | } 173 | var err error 174 | object.Date, err = objectbox.TimeInt64ConvertToEntityProperty(47 * int64(coef)) 175 | if err != nil { 176 | panic(err) 177 | } 178 | 179 | if options != nil && options.PopulateRelations { 180 | object.Related = TestEntityRelated{Name: "rel-" + String} 181 | object.RelatedPtr = &TestEntityRelated{ 182 | Name: "relPtr-" + String, 183 | Next: &EntityByValue{Text: "RelatedPtr-Next-" + String}, 184 | NextSlice: []EntityByValue{{Text: "RelatedPtr-NextSlice-" + String}}, 185 | } 186 | object.RelatedSlice = []EntityByValue{{Text: "relByValue-" + String}} 187 | object.RelatedPtrSlice = []*TestEntityRelated{{ 188 | Name: "relPtr-" + String, 189 | Next: &EntityByValue{Text: "RelatedPtrSlice-Next-" + String}, 190 | NextSlice: []EntityByValue{{Text: "RelatedPtrSlice-NextSlice-" + String}}, 191 | }} 192 | } else { 193 | object.Related.NextSlice = []EntityByValue{} 194 | object.RelatedSlice = []EntityByValue{} 195 | object.RelatedPtrSlice = nil // lazy loaded so Get() would set nil here as well 196 | } 197 | 198 | return object 199 | } 200 | -------------------------------------------------------------------------------- /objectbox/fbutils/utils_test.go: -------------------------------------------------------------------------------- 1 | package fbutils 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | "unsafe" 8 | 9 | "github.com/google/flatbuffers/go" 10 | "github.com/objectbox/objectbox-go/test/assert" 11 | ) 12 | 13 | // This test is here to make sure our flatbuffers integration is correct and mainly focuses on memory management 14 | // because of how the c-api integration is implemented (not copying C void* but mapping it to []byte). 15 | // The test covers all supported types (see entity.go in the test directory). 16 | // It creates an FB object, creates an unsafe copy (similar to the c-arrays.go) and than clears the memory behind that 17 | // unsafe copy. Afterwards, it tests that the "parsed" flatbuffers object is still the same as the written one. 18 | 19 | // this is just a test of the auxiliary functions in this file, so that we can concentrate on actual tests elsewhere 20 | func TestAuxUnsafeBytes(t *testing.T) { 21 | var objBytesManaged = createObjectBytes(object) 22 | assert.Eq(t, 216, len(objBytesManaged)) 23 | assert.Eq(t, 52, int(objBytesManaged[0])) 24 | assert.Eq(t, 51, int(objBytesManaged[57])) 25 | 26 | // get an unsafe copy (really just a pointer), as the the cursor would do 27 | var unsafeBytes = getUnsafeBytes(objBytesManaged) 28 | 29 | // get a safe copy 30 | var safeBytes = make([]byte, len(objBytesManaged)) 31 | copy(safeBytes, objBytesManaged) 32 | 33 | // at this point, they should all be the same 34 | assert.Eq(t, objBytesManaged, unsafeBytes) 35 | assert.NotEq(t, unsafe.Pointer(&objBytesManaged), unsafe.Pointer(&unsafeBytes)) 36 | assert.Eq(t, unsafe.Pointer(&objBytesManaged[0]), unsafe.Pointer(&unsafeBytes[0])) 37 | 38 | assert.Eq(t, objBytesManaged, safeBytes) 39 | assert.NotEq(t, unsafe.Pointer(&objBytesManaged), unsafe.Pointer(&safeBytes)) 40 | 41 | // now let's clear the object bytes, and check the copies 42 | clearBytes(&objBytesManaged) 43 | assert.Eq(t, 216, len(objBytesManaged)) 44 | assert.Eq(t, 0, int(objBytesManaged[0])) 45 | 46 | // now we assert the unsafe bytes has changed if it wasn't supposed to 47 | assert.Eq(t, objBytesManaged, unsafeBytes) 48 | 49 | // but the safe copy is still the same 50 | assert.Eq(t, 52, int(safeBytes[0])) 51 | } 52 | 53 | func TestObjectRead(t *testing.T) { 54 | var objBytesManaged = createObjectBytes(object) 55 | 56 | // get an unsafe copy (really just a pointer), as the the cursor would do 57 | var unsafeBytes = getUnsafeBytes(objBytesManaged) 58 | 59 | // read the object value 60 | var read = readObject(unsafeBytes) 61 | assert.Eq(t, object, read) 62 | 63 | // clear the source bytes 64 | clearBytes(&objBytesManaged) 65 | 66 | // the read object should be still correct 67 | assert.Eq(t, object, read) 68 | fmt.Println(read) 69 | } 70 | 71 | // this simulates what cVoidPtrToByteSlice is doing, i.e. mapping an unmanaged pointer to a new []byte slice 72 | func getUnsafeBytes(source []byte) []byte { 73 | var bytes []byte 74 | header := (*reflect.SliceHeader)(unsafe.Pointer(&bytes)) 75 | header.Data = uintptr(unsafe.Pointer(&source[0])) 76 | header.Len = len(source) 77 | header.Cap = header.Len 78 | return bytes 79 | } 80 | 81 | func clearBytes(data *[]byte) { 82 | for i := range *data { 83 | (*data)[i] = 0 84 | } 85 | } 86 | 87 | // this is a copy of test/model/entity.go 88 | type entity struct { 89 | Id uint64 90 | Int int 91 | Int8 int8 92 | Int16 int16 93 | Int32 int32 94 | Int64 int64 95 | Uint uint 96 | Uint8 uint8 97 | Uint16 uint16 98 | Uint32 uint32 99 | Uint64 uint64 100 | Bool bool 101 | String string 102 | StringVector []string 103 | Byte byte 104 | ByteVector []byte 105 | Rune rune 106 | Float32 float32 107 | Float64 float64 108 | } 109 | 110 | // a prototype object 111 | var object = entity{ 112 | Id: 1, 113 | Int: 2, 114 | Int8: 3, 115 | Int16: 4, 116 | Int32: 5, 117 | Int64: 6, 118 | Uint: 7, 119 | Uint8: 8, 120 | Uint16: 9, 121 | Uint32: 10, 122 | Uint64: 11, 123 | Bool: true, 124 | String: "Str", 125 | StringVector: []string{"First", "second", ""}, 126 | Byte: 12, 127 | ByteVector: []byte{13, 14, 15}, 128 | Rune: 16, 129 | Float32: 17.18, 130 | Float64: 19.20, 131 | } 132 | 133 | func createObjectBytes(obj entity) []byte { 134 | // this is a copy of test/model/entity.obx.go Flatten() 135 | var fbb = flatbuffers.NewBuilder(512) 136 | var offsetString = CreateStringOffset(fbb, obj.String) 137 | var offsetStringVector = CreateStringVectorOffset(fbb, obj.StringVector) 138 | var offsetByteVector = CreateByteVectorOffset(fbb, obj.ByteVector) 139 | 140 | // build the FlatBuffers object 141 | fbb.StartObject(21) 142 | SetUint64Slot(fbb, 0, obj.Id) 143 | SetInt64Slot(fbb, 1, int64(obj.Int)) 144 | SetInt8Slot(fbb, 2, obj.Int8) 145 | SetInt16Slot(fbb, 3, obj.Int16) 146 | SetInt32Slot(fbb, 4, obj.Int32) 147 | SetInt64Slot(fbb, 5, obj.Int64) 148 | SetUint64Slot(fbb, 6, uint64(obj.Uint)) 149 | SetUint8Slot(fbb, 7, obj.Uint8) 150 | SetUint16Slot(fbb, 8, obj.Uint16) 151 | SetUint32Slot(fbb, 9, obj.Uint32) 152 | SetUint64Slot(fbb, 10, obj.Uint64) 153 | SetBoolSlot(fbb, 11, obj.Bool) 154 | SetUOffsetTSlot(fbb, 12, offsetString) 155 | SetUOffsetTSlot(fbb, 20, offsetStringVector) 156 | SetByteSlot(fbb, 13, obj.Byte) 157 | SetUOffsetTSlot(fbb, 14, offsetByteVector) 158 | SetInt32Slot(fbb, 15, obj.Rune) 159 | SetFloat32Slot(fbb, 16, obj.Float32) 160 | SetFloat64Slot(fbb, 17, obj.Float64) 161 | fbb.Finish(fbb.EndObject()) 162 | return fbb.FinishedBytes() 163 | } 164 | 165 | func readObject(data []byte) entity { 166 | // this is a copy of test/model/entity.obx.go Load() 167 | table := &flatbuffers.Table{ 168 | Bytes: data, 169 | Pos: flatbuffers.GetUOffsetT(data), 170 | } 171 | 172 | return entity{ 173 | Id: table.GetUint64Slot(4, 0), 174 | Int: int(table.GetUint64Slot(6, 0)), 175 | Int8: table.GetInt8Slot(8, 0), 176 | Int16: table.GetInt16Slot(10, 0), 177 | Int32: table.GetInt32Slot(12, 0), 178 | Int64: table.GetInt64Slot(14, 0), 179 | Uint: uint(table.GetUint64Slot(16, 0)), 180 | Uint8: table.GetUint8Slot(18, 0), 181 | Uint16: table.GetUint16Slot(20, 0), 182 | Uint32: table.GetUint32Slot(22, 0), 183 | Uint64: table.GetUint64Slot(24, 0), 184 | Bool: table.GetBoolSlot(26, false), 185 | String: GetStringSlot(table, 28), 186 | StringVector: GetStringVectorSlot(table, 44), 187 | Byte: table.GetByteSlot(30, 0), 188 | ByteVector: GetByteVectorSlot(table, 32), 189 | Rune: rune(table.GetInt32Slot(34, 0)), 190 | Float32: table.GetFloat32Slot(36, 0), 191 | Float64: table.GetFloat64Slot(38, 0), 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /objectbox/condition.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | ) 23 | 24 | // Condition is used by Query to limit object selection or specify their order 25 | type Condition interface { 26 | applyTo(qb *QueryBuilder, isRoot bool) (ConditionId, error) 27 | 28 | // Alias sets a string alias for the given condition. It can later be used in Query.Set*Params() methods. 29 | Alias(alias string) Condition 30 | 31 | // As sets a string alias for the given condition. It can later be used in Query.Set*Params() methods. 32 | As(alias *alias) Condition 33 | } 34 | 35 | // ConditionId is a condition identifier type, used when building queries 36 | type ConditionId = int32 37 | 38 | const conditionIdFakeOrder = -1 39 | const conditionIdFakeLink = -2 40 | 41 | type conditionClosure struct { 42 | apply func(qb *QueryBuilder) (ConditionId, error) 43 | alias *string 44 | } 45 | 46 | func (condition *conditionClosure) applyTo(qb *QueryBuilder, isRoot bool) (ConditionId, error) { 47 | cid, err := condition.apply(qb) 48 | if err != nil { 49 | return 0, err 50 | } 51 | 52 | if condition.alias != nil { 53 | err = qb.Alias(*condition.alias) 54 | if err != nil { 55 | return 0, err 56 | } 57 | } 58 | 59 | return cid, nil 60 | } 61 | 62 | // Alias sets a string alias for the given condition. It can later be used in Query.Set*Params() methods 63 | func (condition *conditionClosure) Alias(alias string) Condition { 64 | condition.alias = &alias 65 | return condition 66 | } 67 | 68 | // As sets an alias for the given condition. It can later be used in Query.Set*Params() methods. 69 | func (condition *conditionClosure) As(alias *alias) Condition { 70 | condition.alias = alias.alias() 71 | return condition 72 | } 73 | 74 | // Combines multiple conditions with an operator 75 | type conditionCombination struct { 76 | or bool // AND by default 77 | conditions []Condition 78 | alias *string // this is only used to report an error 79 | } 80 | 81 | // assertNoLinks makes sure there are no links (0 condition IDs) among given conditions 82 | func (*conditionCombination) assertNoLinks(conditionIds []ConditionId) error { 83 | for _, cid := range conditionIds { 84 | if cid == conditionIdFakeLink { 85 | return errors.New("using Link inside Any/All is not supported") 86 | } 87 | } 88 | return nil 89 | } 90 | 91 | func (condition *conditionCombination) applyTo(qb *QueryBuilder, isRoot bool) (ConditionId, error) { 92 | if condition.alias != nil { 93 | return 0, fmt.Errorf("using Alias/As(\"%s\") on a combination of conditions is not supported", *condition.alias) 94 | } 95 | 96 | if len(condition.conditions) == 0 { 97 | return 0, nil 98 | } else if len(condition.conditions) == 1 { 99 | return condition.conditions[0].applyTo(qb, isRoot) 100 | } 101 | 102 | ids := make([]ConditionId, 0, len(condition.conditions)) 103 | for _, sub := range condition.conditions { 104 | cid, err := sub.applyTo(qb, false) 105 | if err != nil { 106 | return 0, err 107 | } 108 | 109 | // Skip order pseudo conditions. 110 | // Note: conditionIdFakeLink is allowed here and is caught below if used in non-root or in an "ALL" combination. 111 | if cid != conditionIdFakeOrder { 112 | ids = append(ids, cid) 113 | } 114 | } 115 | 116 | // root All (AND) is implicit so no need to actually combine the conditions 117 | if isRoot && !condition.or { 118 | return 0, nil 119 | } 120 | 121 | if err := condition.assertNoLinks(ids); err != nil { 122 | return 0, err 123 | } 124 | 125 | if condition.or { 126 | return qb.Any(ids) 127 | } 128 | 129 | return qb.All(ids) 130 | } 131 | 132 | // Alias sets a string alias for the given condition. It can later be used in Query.Set*Params() methods. 133 | // This is an invalid call on a combination of conditions and will result in an error. 134 | func (condition *conditionCombination) Alias(alias string) Condition { 135 | condition.alias = &alias 136 | return condition 137 | } 138 | 139 | // As sets an alias for the given condition. It can later be used in Query.Set*Params() methods. 140 | // This is an invalid call on a combination of conditions and will result in an error. 141 | func (condition *conditionCombination) As(alias *alias) Condition { 142 | condition.alias = alias.alias() 143 | return condition 144 | } 145 | 146 | // Any provides a way to combine multiple query conditions (equivalent to OR logical operator) 147 | func Any(conditions ...Condition) Condition { 148 | return &conditionCombination{ 149 | or: true, 150 | conditions: conditions, 151 | } 152 | } 153 | 154 | // All provides a way to combine multiple query conditions (equivalent to AND logical operator) 155 | func All(conditions ...Condition) Condition { 156 | return &conditionCombination{ 157 | conditions: conditions, 158 | } 159 | } 160 | 161 | // implements propertyOrAlias 162 | type alias struct { 163 | string 164 | } 165 | 166 | func (alias) propertyId() TypeId { 167 | return 0 168 | } 169 | 170 | func (alias) entityId() TypeId { 171 | return 0 172 | } 173 | 174 | func (as *alias) alias() *string { 175 | return &as.string 176 | } 177 | 178 | // Alias wraps a string as an identifier usable for Query.Set*Params*() methods. 179 | func Alias(value string) *alias { 180 | return &alias{value} 181 | } 182 | 183 | type orderClosure struct { 184 | apply func(qb *QueryBuilder) error 185 | alias *string // this is only used to report an error 186 | } 187 | 188 | func (order *orderClosure) applyTo(qb *QueryBuilder, isRoot bool) (ConditionId, error) { 189 | if order.alias != nil { 190 | return 0, fmt.Errorf("using Alias/As(\"%s\") on Order*() is not supported", *order.alias) 191 | } 192 | 193 | return conditionIdFakeOrder, order.apply(qb) 194 | } 195 | 196 | // Alias sets a string alias for the given condition. It can later be used in Query.Set*Params() methods. 197 | // This is an invalid call on order definition and will result in an error. 198 | func (order *orderClosure) Alias(alias string) Condition { 199 | order.alias = &alias 200 | return order 201 | } 202 | 203 | // As sets an alias for the given condition. It can later be used in Query.Set*Params() methods. 204 | // This is an invalid call on order definition and will result in an error. 205 | func (order *orderClosure) As(alias *alias) Condition { 206 | order.alias = alias.alias() 207 | return order 208 | } 209 | -------------------------------------------------------------------------------- /objectbox/asyncbox.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | /* 20 | #include <stdlib.h> 21 | #include "objectbox.h" 22 | */ 23 | import "C" 24 | import ( 25 | "errors" 26 | "unsafe" 27 | ) 28 | 29 | // AsyncBox provides asynchronous operations on objects of a common type. 30 | // 31 | // Asynchronous operations are executed on a separate internal thread for better performance. 32 | // 33 | // There are two main use cases: 34 | // 35 | // 1) "execute & forget:" you gain faster put/remove operations as you don't have to wait for the transaction to finish. 36 | // 37 | // 2) Many small transactions: if your write load is typically a lot of individual puts that happen in parallel, 38 | // this will merge small transactions into bigger ones. This results in a significant gain in overall throughput. 39 | // 40 | // In situations with (extremely) high async load, an async method may be throttled (~1ms) or delayed up to 1 second. 41 | // In the unlikely event that the object could still not be enqueued (full queue), an error will be returned. 42 | // 43 | // Note that async methods do not give you hard durability guarantees like the synchronous Box provides. 44 | // There is a small time window in which the data may not have been committed durably yet. 45 | type AsyncBox struct { 46 | box *Box 47 | cAsync *C.OBX_async 48 | cOwned bool // whether the cAsync resource is owned by this struct 49 | } 50 | 51 | // NewAsyncBox creates a new async box with the given operation timeout in case an async queue is full. 52 | // The returned struct must be freed explicitly using the Close() method. 53 | // It's usually preferable to use Box::Async() which takes care of resource management and doesn't require closing. 54 | func NewAsyncBox(ob *ObjectBox, entityId TypeId, timeoutMs uint64) (*AsyncBox, error) { 55 | var async = &AsyncBox{ 56 | box: ob.InternalBox(entityId), 57 | cOwned: true, 58 | } 59 | 60 | if err := cCallBool(func() bool { 61 | async.cAsync = C.obx_async_create(async.box.cBox, C.uint64_t(timeoutMs)) 62 | return async.cAsync != nil 63 | }); err != nil { 64 | return nil, err 65 | } 66 | 67 | return async, nil 68 | } 69 | 70 | // Close frees resources of a customized AsyncBox (e.g. with a custom timeout). 71 | // Not necessary for the standard (shared) instance from box.Async(); Close() can still be called for those: 72 | // it just won't have any effect. 73 | func (async *AsyncBox) Close() error { 74 | if !async.cOwned || async.cAsync == nil { 75 | return nil 76 | } 77 | var cAsync = async.cAsync 78 | async.cAsync = nil 79 | return cCall(func() C.obx_err { 80 | return C.obx_async_close(cAsync) 81 | }) 82 | } 83 | 84 | func (async *AsyncBox) put(object interface{}, mode int) (uint64, error) { 85 | entity := async.box.entity 86 | idFromObject, err := entity.binding.GetId(object) 87 | if err != nil { 88 | return 0, err 89 | } 90 | 91 | if entity.hasRelations { 92 | return 0, errors.New("asynchronous Put/Insert/Update is currently not supported on entities that have" + 93 | " relations because it could result in partial inserts/broken relations") 94 | } 95 | 96 | id, err := async.box.idForPut(idFromObject) 97 | if err != nil { 98 | return 0, err 99 | } 100 | 101 | err = async.box.withObjectBytes(object, id, func(bytes []byte) error { 102 | return cCall(func() C.obx_err { 103 | return C.obx_async_put5(async.cAsync, C.obx_id(id), unsafe.Pointer(&bytes[0]), C.size_t(len(bytes)), 104 | C.OBXPutMode(mode)) 105 | }) 106 | }) 107 | 108 | if err != nil { 109 | return 0, err 110 | } 111 | 112 | // update the id on the object 113 | if idFromObject != id { 114 | entity.binding.SetId(object, id) 115 | } 116 | 117 | return id, nil 118 | } 119 | 120 | // Put inserts/updates a single object asynchronously. 121 | // When inserting a new object, the ID property on the passed object will be assigned a new ID the entity would hold 122 | // if the insert is ultimately successful. The newly assigned ID may not become valid if the insert fails. 123 | func (async *AsyncBox) Put(object interface{}) (id uint64, err error) { 124 | return async.put(object, cPutModePut) 125 | } 126 | 127 | // Insert a single object asynchronously. 128 | // The ID property on the passed object will be assigned a new ID the entity would hold if the insert is ultimately 129 | // successful. The newly assigned ID may not become valid if the insert fails. 130 | // Fails silently if an object with the same ID already exists (this error is not returned). 131 | func (async *AsyncBox) Insert(object interface{}) (id uint64, err error) { 132 | return async.put(object, cPutModeInsert) 133 | } 134 | 135 | // Update a single object asynchronously. 136 | // The object must already exists or the update fails silently (without an error returned). 137 | func (async *AsyncBox) Update(object interface{}) error { 138 | _, err := async.put(object, cPutModeUpdate) 139 | return err 140 | } 141 | 142 | // Remove deletes a single object asynchronously. 143 | func (async *AsyncBox) Remove(object interface{}) error { 144 | id, err := async.box.entity.binding.GetId(object) 145 | if err != nil { 146 | return err 147 | } 148 | 149 | return async.RemoveId(id) 150 | } 151 | 152 | // RemoveId deletes a single object asynchronously. 153 | func (async *AsyncBox) RemoveId(id uint64) error { 154 | return cCall(func() C.obx_err { 155 | return C.obx_async_remove(async.cAsync, C.obx_id(id)) 156 | }) 157 | } 158 | 159 | // AwaitCompletion waits for all (including future) async submissions to be completed (the async queue becomes idle for 160 | // a moment). Currently this is not limited to the single entity this AsyncBox is working on but all entities in the 161 | // store. Returns an error if shutting down or an error occurred 162 | func (async *AsyncBox) AwaitCompletion() error { 163 | return cCallBool(func() bool { 164 | return bool(C.obx_store_await_async_completion(async.box.ObjectBox.store)) 165 | }) 166 | } 167 | 168 | // AwaitSubmitted for previously submitted async operations to be completed (the async queue does not have to become idle). 169 | // Currently this is not limited to the single entity this AsyncBox is working on but all entities in the store. 170 | // Returns an error if shutting down or an error occurred 171 | func (async *AsyncBox) AwaitSubmitted() error { 172 | return cCallBool(func() bool { 173 | return bool(C.obx_store_await_async_submitted(async.box.ObjectBox.store)) 174 | }) 175 | } 176 | -------------------------------------------------------------------------------- /test/assert/assert.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package assert 18 | 19 | import ( 20 | "fmt" 21 | "path/filepath" 22 | "reflect" 23 | "regexp" 24 | "runtime" 25 | "testing" 26 | "time" 27 | ) 28 | 29 | // True asserts that the given value is a boolean true 30 | func True(t *testing.T, actual interface{}) { 31 | if actual != true { 32 | Failf(t, "Expected: true\nReceived: %v", actual) 33 | } 34 | } 35 | 36 | // Eq asserts the values are equal. Uses reflect.DeepEqual to test for equality 37 | func Eq(t *testing.T, expected interface{}, actual interface{}) { 38 | if expected == nil && actual == nil { 39 | return 40 | } 41 | if !reflect.DeepEqual(expected, actual) { 42 | Failf(t, "Values are not equal\nExpected: %v\nReceived: %v", expected, actual) 43 | } 44 | } 45 | 46 | // EqItems asserts the given slices have the same elements (regardless of their order) 47 | func EqItems(t *testing.T, expected interface{}, actual interface{}) { 48 | var exp = reflect.ValueOf(expected) 49 | var act = reflect.ValueOf(actual) 50 | 51 | if exp.Type() != act.Type() { 52 | Failf(t, "Types are not equal\nExpected: %v\nReceived: %v", exp.Type(), act.Type()) 53 | } 54 | 55 | if exp.Len() != act.Len() { 56 | Failf(t, "Lengths are not equal\nExpected: %v (%d elements)\nReceived: %v (%d elements)", exp, exp.Len(), act, act.Len()) 57 | } 58 | 59 | if exp.Len() == 0 { 60 | return 61 | } 62 | 63 | // make a map[elem-type]int = number of occurrences of each element 64 | // we use reflection to create a dynamically typed map 65 | var keyType = exp.Index(0).Type() 66 | var valueType = reflect.TypeOf(int(0)) 67 | var mapType = reflect.MapOf(keyType, valueType) 68 | merged := reflect.MakeMapWithSize(mapType, exp.Len()) 69 | 70 | // count the number of expected occurrences 71 | for i := 0; i < exp.Len(); i++ { 72 | var existing = merged.MapIndex(exp.Index(i)) 73 | if existing.IsValid() { 74 | merged.SetMapIndex(exp.Index(i), reflect.ValueOf(int(existing.Int())+1)) // increase by one 75 | } else { 76 | merged.SetMapIndex(exp.Index(i), reflect.ValueOf(int(1))) 77 | } 78 | } 79 | 80 | // count the number of actual occurrences 81 | for i := 0; i < act.Len(); i++ { 82 | var existing = merged.MapIndex(act.Index(i)) 83 | if !existing.IsValid() { 84 | Failf(t, "Unexpected item %d: %v found in %v, expecting %v", i, act.Index(i), act, exp) 85 | } 86 | 87 | merged.SetMapIndex(act.Index(i), reflect.ValueOf(int(existing.Int())-1)) // decrease by one 88 | } 89 | 90 | // check if all of the expected where actually found 91 | for _, k := range merged.MapKeys() { 92 | var existing = merged.MapIndex(k) 93 | if existing.Int() != 0 { 94 | Failf(t, "Expected %v more of item %v", existing.Int(), k) 95 | } 96 | } 97 | } 98 | 99 | // NotEq asserts the given values are not equal. Uses reflect.DeepEqual to test for equality 100 | func NotEq(t *testing.T, notThisValue interface{}, actual interface{}) { 101 | if reflect.DeepEqual(notThisValue, actual) { 102 | Failf(t, "Expected a value other than %v", notThisValue) 103 | } 104 | } 105 | 106 | // Err asserts the error is not nil 107 | func Err(t *testing.T, err error) { 108 | if err == nil { 109 | Failf(t, "Expected error hasn't occurred: %v", err) 110 | } 111 | } 112 | 113 | // NoErr asserts the error is nil 114 | func NoErr(t *testing.T, err error) { 115 | if err != nil { 116 | Failf(t, "Unexpected error occurred: %v", err) 117 | } 118 | } 119 | 120 | // NotNil verifies that the given value is not nil 121 | func NotNil(t *testing.T, actual interface{}) { 122 | var act = reflect.ValueOf(actual) 123 | if act.IsNil() { 124 | Failf(t, "Unexpected nil, type %v", act.Type()) 125 | } 126 | } 127 | 128 | // Failf fails immediately 129 | func Failf(t *testing.T, format string, args ...interface{}) { 130 | Fail(t, fmt.Sprintf(format, args...)) 131 | } 132 | 133 | // Fail fails immediately 134 | func Fail(t *testing.T, text string) { 135 | stackString := "Call stack:\n" 136 | for idx := 1; ; idx++ { 137 | _, file, line, ok := runtime.Caller(idx) 138 | if !ok { 139 | break 140 | } 141 | _, filename := filepath.Split(file) 142 | if filename == "assert.go" { 143 | continue 144 | } 145 | if filename == "testing.go" { 146 | break 147 | } 148 | stackString += fmt.Sprintf("%v:%v\n", filename, line) 149 | } 150 | if t != nil { 151 | t.Fatal(text, "\n", stackString) 152 | } else { 153 | fmt.Print(text, "\n", stackString) 154 | } 155 | } 156 | 157 | // MustMatch checks the value against a given regular expression. 158 | func MustMatch(t *testing.T, match *regexp.Regexp, value interface{}) { 159 | var str = fmt.Sprint(value) 160 | if !match.MatchString(str) { 161 | Failf(t, "Doesn't match regexp\nExpected: '%s'\nReceived: '%s'", match.String(), str) 162 | } 163 | } 164 | 165 | // MustPanic ensures that the caller's context will panic and that the panic will match the given regular expression 166 | // func() { 167 | // defer mustPanic(t, regexp.MustCompile("+*")) 168 | // panic("some text") 169 | // } 170 | func MustPanic(t *testing.T, match *regexp.Regexp) { 171 | if r := recover(); r != nil { 172 | // convert panic result to string 173 | var str string 174 | switch x := r.(type) { 175 | case string: 176 | str = x 177 | case error: 178 | str = x.Error() 179 | default: 180 | Failf(t, "Unknown panic result '%v' for an expected panic: %s", r, match.String()) 181 | } 182 | 183 | if !match.MatchString(str) { 184 | Failf(t, "Errors are not equal\nExpected: panic '%s'\nReceived: '%s'", match.String(), str) 185 | } 186 | } else { 187 | Failf(t, "Expected panic hasn't occurred: %s", match.String()) 188 | } 189 | } 190 | 191 | // StringChannelExpect that the given channel receives the expected string next, with the given timeout 192 | func StringChannelExpect(t *testing.T, expected string, channel chan string, timeout time.Duration) { 193 | select { 194 | case received := <-channel: 195 | Eq(t, expected, received) 196 | case <-time.After(timeout): 197 | Failf(t, "Waiting for '%s' on the channel timed out after %v", expected, timeout) 198 | } 199 | } 200 | 201 | func StringChannelMustTimeout(t *testing.T, channel chan string, timeout time.Duration) { 202 | select { 203 | case received := <-channel: 204 | Failf(t, "Received an unexpected value '%s' on the channel. Instead, it was expected to time out after %v", received, timeout) 205 | case <-time.After(timeout): 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /objectbox/c-callbacks.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | /* 20 | This file implements some universal formats of C callbacks forwarding to Go callbacks 21 | 22 | Overview: 23 | * Register a callback, getting a callback ID. 24 | * Pass the registered callback ID together with a generic C callback (e.g. C.cVoidCallbackDispatch) to a C.obx_* function. 25 | * When ObjectBox calls C.cVoidCallbackDispatch, it finds the callback registered under that ID and calls it. 26 | * After there can be no more callbacks, the callback must be unregistered. 27 | 28 | Code example: 29 | callbackId, err := cCallbackRegister(cVoidCallback(func() { // cVoidCallback() is a type-cast 30 | // do your thing here 31 | })) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | // don't forget to unregister the callback after it's no longer going to be called or you would fill the queue up quickly 37 | defer cCallbackUnregister(callbackId) 38 | 39 | rc := C.obx_callback_taking_method(cStruct, (*C.actual_fn_type_required_by_c_method)(cVoidCallbackDispatch), unsafe.Pointer(&callbackId)) 40 | */ 41 | 42 | /* 43 | #include "objectbox.h" 44 | 45 | // following functions implement forwarding and are passed to the c-api 46 | 47 | // void return, no arguments 48 | extern void cVoidCallbackDispatch(uintptr_t callbackId); 49 | typedef void cVoidCallback(uintptr_t callbackId); 50 | 51 | // void return, uint64 argument 52 | extern void cVoidUint64CallbackDispatch(uintptr_t callbackId); 53 | typedef void cVoidUint64Callback(uintptr_t callbackId, uint64_t arg); 54 | 55 | // void return, int64 argument 56 | extern void cVoidInt64CallbackDispatch(uintptr_t callbackId); 57 | typedef void cVoidInt64Callback(uintptr_t callbackId, int64_t arg); 58 | 59 | // void return, const uintptr_t argument 60 | extern void cVoidConstVoidCallbackDispatch(uintptr_t callbackId); 61 | typedef void cVoidConstVoidCallback(uintptr_t callbackId, const void* arg); 62 | */ 63 | import "C" 64 | import ( 65 | "fmt" 66 | "sync" 67 | "unsafe" 68 | ) 69 | 70 | // cCallable allows us to avoid defining below register/unregister/lookup methods for all possible function signatures. 71 | // This interface is implemented by callback functions and each function signature should only implement a single method 72 | // and panic in all others. Method name format used below: "call<ReturnType><...ArgNType>()" 73 | type cCallable interface { 74 | callVoid() 75 | callVoidUint64(uint64) 76 | callVoidInt64(int64) 77 | callVoidConstVoid(unsafe.Pointer) 78 | } 79 | 80 | // programming error - using an incorrect `cCallable` (arguments and return-type combination) 81 | const cCallablePanicMsg = "invalid callback signature" 82 | 83 | type cVoidCallback func() 84 | 85 | func (fn cVoidCallback) callVoid() { fn() } 86 | func (fn cVoidCallback) callVoidUint64(uint64) { panic(cCallablePanicMsg) } 87 | func (fn cVoidCallback) callVoidInt64(int64) { panic(cCallablePanicMsg) } 88 | func (fn cVoidCallback) callVoidConstVoid(unsafe.Pointer) { panic(cCallablePanicMsg) } 89 | 90 | var cVoidCallbackDispatchPtr = (*C.cVoidCallback)(unsafe.Pointer(C.cVoidCallbackDispatch)) 91 | 92 | type cVoidUint64Callback func(uint64) 93 | 94 | func (fn cVoidUint64Callback) callVoid() { panic(cCallablePanicMsg) } 95 | func (fn cVoidUint64Callback) callVoidUint64(arg uint64) { fn(arg) } 96 | func (fn cVoidUint64Callback) callVoidInt64(int64) { panic(cCallablePanicMsg) } 97 | func (fn cVoidUint64Callback) callVoidConstVoid(unsafe.Pointer) { panic(cCallablePanicMsg) } 98 | 99 | var cVoidUint64CallbackDispatchPtr = (*C.cVoidUint64Callback)(unsafe.Pointer(C.cVoidUint64CallbackDispatch)) 100 | 101 | type cVoidInt64Callback func(int64) 102 | 103 | func (fn cVoidInt64Callback) callVoid() { panic(cCallablePanicMsg) } 104 | func (fn cVoidInt64Callback) callVoidUint64(uint64) { panic(cCallablePanicMsg) } 105 | func (fn cVoidInt64Callback) callVoidInt64(arg int64) { fn(arg) } 106 | func (fn cVoidInt64Callback) callVoidConstVoid(unsafe.Pointer) { panic(cCallablePanicMsg) } 107 | 108 | var cVoidInt64CallbackDispatchPtr = (*C.cVoidInt64Callback)(unsafe.Pointer(C.cVoidInt64CallbackDispatch)) 109 | 110 | type cVoidConstVoidCallback func(unsafe.Pointer) 111 | 112 | func (fn cVoidConstVoidCallback) callVoid() { panic(cCallablePanicMsg) } 113 | func (fn cVoidConstVoidCallback) callVoidUint64(uint64) { panic(cCallablePanicMsg) } 114 | func (fn cVoidConstVoidCallback) callVoidInt64(int64) { panic(cCallablePanicMsg) } 115 | func (fn cVoidConstVoidCallback) callVoidConstVoid(arg unsafe.Pointer) { fn(arg) } 116 | 117 | var cVoidConstVoidCallbackDispatchPtr = (*C.cVoidConstVoidCallback)(unsafe.Pointer(C.cVoidConstVoidCallbackDispatch)) 118 | 119 | type cCallbackId uint32 120 | 121 | var cCallbackLastId cCallbackId 122 | var cCallbackMutex sync.Mutex 123 | var cCallbackMap = make(map[cCallbackId]cCallable) 124 | 125 | // The result is actually not a memory pointer, just a number. That's also how it's used in cCallbackLookup(). 126 | func (cbId cCallbackId) cPtr() unsafe.Pointer { 127 | //goland:noinspection GoVetUnsafePointer 128 | return unsafe.Pointer(uintptr(cbId)) 129 | } 130 | 131 | // Returns the next cCallbackId in a sequence (NOT checking its availability), skipping zero. 132 | func cCallbackNextId() cCallbackId { 133 | cCallbackLastId++ 134 | if cCallbackLastId == 0 { 135 | cCallbackLastId++ 136 | } 137 | return cCallbackLastId 138 | } 139 | 140 | func cCallbackRegister(fn cCallable) (cCallbackId, error) { 141 | cCallbackMutex.Lock() 142 | defer cCallbackMutex.Unlock() 143 | 144 | // cycle through ids until we find an empty slot 145 | var initialId = cCallbackNextId() 146 | for cCallbackMap[cCallbackLastId] != nil { 147 | cCallbackNextId() 148 | 149 | if initialId == cCallbackLastId { 150 | return 0, fmt.Errorf("full queue of data-callback callbacks - can't allocate another") 151 | } 152 | } 153 | 154 | cCallbackMap[cCallbackLastId] = fn 155 | return cCallbackLastId, nil 156 | } 157 | 158 | func cCallbackLookup(id C.uintptr_t) cCallable { 159 | cCallbackMutex.Lock() 160 | defer cCallbackMutex.Unlock() 161 | 162 | fn, found := cCallbackMap[cCallbackId(id)] 163 | if !found { 164 | // this might happen in extraordinary circumstances, e.g. during shutdown if there are still some sync listeners 165 | fmt.Println(fmt.Errorf("invalid C-API callback ID %d", id)) 166 | return nil 167 | } 168 | 169 | return fn 170 | } 171 | 172 | func cCallbackUnregister(id cCallbackId) { 173 | // special value - not registered 174 | if id == 0 { 175 | return 176 | } 177 | 178 | cCallbackMutex.Lock() 179 | defer cCallbackMutex.Unlock() 180 | 181 | delete(cCallbackMap, id) 182 | } 183 | -------------------------------------------------------------------------------- /objectbox/objectbox.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2021 ObjectBox Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package objectbox 18 | 19 | /* 20 | #cgo LDFLAGS: -lobjectbox 21 | #include <stdlib.h> 22 | #include "objectbox.h" 23 | */ 24 | import "C" 25 | 26 | import ( 27 | "errors" 28 | "fmt" 29 | "runtime" 30 | "strconv" 31 | "sync" 32 | ) 33 | 34 | const ( 35 | // DebugflagsLogTransactionsRead enable read transaction logging 36 | DebugflagsLogTransactionsRead = 1 37 | 38 | // DebugflagsLogTransactionsWrite enable write transaction logging 39 | DebugflagsLogTransactionsWrite = 2 40 | 41 | // DebugflagsLogQueries enable query logging 42 | DebugflagsLogQueries = 4 43 | 44 | // DebugflagsLogQueryParameters enable query parameters logging 45 | DebugflagsLogQueryParameters = 8 46 | 47 | // DebugflagsLogAsyncQueue enable async operations logging 48 | DebugflagsLogAsyncQueue = 16 49 | ) 50 | 51 | const ( 52 | // Standard put ("insert or update") 53 | cPutModePut = 1 54 | 55 | // Put succeeds only if the entity does not exist yet. 56 | cPutModeInsert = 2 57 | 58 | // Put succeeds only if the entity already exist. 59 | cPutModeUpdate = 3 60 | 61 | // Not used yet (does not make sense for asnyc puts) 62 | // The given ID (non-zero) is guaranteed to be new; don't use unless you know exactly what you are doing! 63 | // This is primarily used internally. Wrong usage leads to inconsistent data (e.g. index data not updated)! 64 | cPutModePutIdGuaranteedToBeNew = 4 65 | ) 66 | 67 | // atomic boolean true & false 68 | const aTrue = 1 69 | const aFalse = 0 70 | 71 | // TypeId is a type of an internal ID on model/property/relation/index 72 | type TypeId uint32 73 | 74 | // ObjectBox provides super-fast object storage 75 | type ObjectBox struct { 76 | store *C.OBX_store 77 | entitiesById map[TypeId]*entity 78 | entitiesByName map[string]*entity 79 | boxes map[TypeId]*Box 80 | boxesMutex sync.Mutex 81 | options options 82 | syncClient *SyncClient 83 | } 84 | 85 | type options struct { 86 | asyncTimeout uint 87 | } 88 | 89 | // constant during runtime so no need to call this each time it's necessary 90 | var supportsResultArray = bool(C.obx_has_feature(C.OBXFeature_ResultArray)) 91 | 92 | // Close fully closes the database and frees resources 93 | func (ob *ObjectBox) Close() { 94 | storeToClose := ob.store 95 | ob.store = nil 96 | if ob.syncClient != nil { 97 | _ = ob.syncClient.Close() 98 | } 99 | if storeToClose != nil { 100 | C.obx_store_close(storeToClose) 101 | } 102 | } 103 | 104 | // RunInReadTx executes the given function inside a read transaction. 105 | // The execution of the function `fn` must be sequential and executed in the same thread, which is enforced internally. 106 | // If you launch goroutines inside `fn`, they will be executed on separate threads and not part of the same transaction. 107 | // Multiple read transaction may be executed concurrently. 108 | // The error returned by your callback is passed-through as the output error 109 | func (ob *ObjectBox) RunInReadTx(fn func() error) error { 110 | return ob.runInTxn(true, fn) 111 | } 112 | 113 | // RunInWriteTx executes the given function inside a write transaction. 114 | // The execution of the function `fn` must be sequential and executed in the same thread, which is enforced internally. 115 | // If you launch goroutines inside `fn`, they will be executed on separate threads and not part of the same transaction. 116 | // Only one write transaction may be active at a time (concurrently). 117 | // The error returned by your callback is passed-through as the output error. 118 | // If the resulting error is not nil, the transaction is aborted (rolled-back) 119 | func (ob *ObjectBox) RunInWriteTx(fn func() error) error { 120 | return ob.runInTxn(false, fn) 121 | } 122 | 123 | func (ob *ObjectBox) runInTxn(readOnly bool, fn func() error) (err error) { 124 | // NOTE if runtime.LockOSThread() is about to be removed, evaluate use of createError() inside transactions 125 | runtime.LockOSThread() 126 | 127 | var cTxn *C.OBX_txn 128 | if readOnly { 129 | cTxn = C.obx_txn_read(ob.store) 130 | } else { 131 | cTxn = C.obx_txn_write(ob.store) 132 | } 133 | 134 | if cTxn == nil { 135 | err = createError() 136 | runtime.UnlockOSThread() 137 | return err 138 | } 139 | 140 | // Defer to ensure a TX is ALWAYS closed, even in a panic 141 | defer func() { 142 | if cTxn != nil { 143 | if rc := C.obx_txn_close(cTxn); rc != 0 { 144 | if err == nil { 145 | err = createError() 146 | } else { 147 | err = fmt.Errorf("%s; %s", err, createError()) 148 | } 149 | } 150 | } 151 | 152 | runtime.UnlockOSThread() 153 | }() 154 | 155 | err = fn() 156 | 157 | if !readOnly && err == nil { 158 | var ptr = cTxn 159 | cTxn = nil 160 | if rc := C.obx_txn_success(ptr); rc != 0 { 161 | err = createError() 162 | } 163 | } 164 | 165 | return err 166 | } 167 | 168 | func (ob *ObjectBox) getEntityById(id TypeId) *entity { 169 | entity := ob.entitiesById[id] 170 | if entity == nil { 171 | // Configuration error by the dev, OK to panic 172 | panic("Configuration error; no entity registered for entity ID " + strconv.Itoa(int(id))) 173 | } 174 | return entity 175 | } 176 | 177 | func (ob *ObjectBox) getEntityByName(name string) *entity { 178 | entity := ob.entitiesByName[name] 179 | if entity == nil { 180 | // Configuration error by the dev, OK to panic 181 | panic("Configuration error; no entity registered for entity name " + name) 182 | } 183 | return entity 184 | } 185 | 186 | // SetDebugFlags configures debug logging of the ObjectBox core. 187 | // See DebugFlags* constants 188 | func (ob *ObjectBox) SetDebugFlags(flags uint) error { 189 | return cCall(func() C.obx_err { 190 | return C.obx_store_debug_flags(ob.store, C.uint32_t(flags)) 191 | }) 192 | } 193 | 194 | // InternalBox returns an Entity Box or panics on error (in case entity with the given ID doesn't exist) 195 | func (ob *ObjectBox) InternalBox(entityId TypeId) *Box { 196 | box, err := ob.box(entityId) 197 | if err != nil { 198 | panic(fmt.Sprintf("Could not create box for entity ID %d: %s", entityId, err)) 199 | } 200 | return box 201 | } 202 | 203 | // Gets an Entity Box which provides CRUD access to objects of the given type 204 | func (ob *ObjectBox) box(entityId TypeId) (*Box, error) { 205 | ob.boxesMutex.Lock() 206 | defer ob.boxesMutex.Unlock() 207 | 208 | if box := ob.boxes[entityId]; box != nil { 209 | return box, nil 210 | } 211 | 212 | box, err := newBox(ob, entityId) 213 | if err != nil { 214 | return nil, err 215 | } 216 | 217 | ob.boxes[entityId] = box 218 | return box, nil 219 | } 220 | 221 | // AwaitAsyncCompletion blocks until all PutAsync insert have been processed 222 | func (ob *ObjectBox) AwaitAsyncCompletion() error { 223 | return cCallBool(func() bool { 224 | return bool(C.obx_store_await_async_completion(ob.store)) 225 | }) 226 | } 227 | 228 | // SyncClient returns an existing client associated with the store or nil if not available. 229 | // Use NewSyncClient() to create it the first time. 230 | func (ob *ObjectBox) SyncClient() (*SyncClient, error) { 231 | if ob.syncClient == nil { 232 | return nil, errors.New("this store doesn't have a SyncClient associated, use NewSyncClient() to create one") 233 | } 234 | return ob.syncClient, nil 235 | } 236 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | <p align="center"><img width="466" src=https://user-images.githubusercontent.com/91467067/190631181-ac090b78-f917-49f2-9293-0f1efa82db18.png></p> 2 | 3 | <p align="center"> 4 | <a href="https://golang.objectbox.io/install">Getting Started</a> • 5 | <a href="https://golang.objectbox.io">Documentation</a> • 6 | <a href="https://github.com/objectbox/objectbox-go/tree/main/examples">Example Apps</a> • 7 | <a href="https://github.com/objectbox/objectbox-go/issues">Issues</a> 8 | </p> 9 | 10 | <p align="center"> 11 | <a href="https://goreportcard.com/report/github.com/objectbox/objectbox-go"> 12 | <img src="https://goreportcard.com/badge/github.com/objectbox/objectbox-go?style=flat-square" alt="Go Report"> 13 | </a> 14 | <a href="https://pkg.go.dev/github.com/objectbox/objectbox-go/objectbox"> 15 | <img src="https://img.shields.io/badge/godoc-objectbox-00b0d7?style=flat-square&logo=go" alt="Godocs"> 16 | </a> 17 | <a href="https://golang.objectbox.io/#changelog"> 18 | <img src="https://img.shields.io/github/v/release/objectbox/objectbox-go?color=17A6A6&style=flat-square" alt="Latest Release"> 19 | </a> 20 | <a href="https://twitter.com/ObjectBox_io"> 21 | <img src="https://img.shields.io/twitter/follow/objectbox_io?color=%20%2300aced&logo=twitter&style=flat-square" alt="Follow @ObjectBox_io"> 22 | </a> 23 | </p> 24 | 25 | ObjectBox Go Database - simple but powerful; frugal but fast 26 | ========================= 27 | The Golang database is easy to use via an intuitive native Golang API and persists objects superfast and sustainably.\ 28 | Go and check out [the performance benchmarks vs SQLite (GORM) & Storm](https://objectbox.io/go-1-0-release-and-performance-benchmarks/). 29 | 30 | ObjectBox persists your native Go structs using a simple CRUD API: 31 | 32 | ```go 33 | id, err := box.Put(&Person{ FirstName: "Joe", LastName: "Green" }) 34 | ``` 35 | 36 | Want details? **[Read the docs](https://golang.objectbox.io/)** or 37 | **[check out the API reference](https://godoc.org/github.com/objectbox/objectbox-go/objectbox)**. 38 | 39 | Latest release: [v1.9.0 (2025-03-12)](https://golang.objectbox.io/) 40 | 41 | ## Table of Contents: 42 | - [High-performance Golang database](#high-performance-golang-database) 43 | - [Getting started](#getting-started) 44 | - [Already using ObjectBox?](#already-using-objectbox) 45 | - [Upgrading to a newer version](#upgrading-to-a-newer-version) 46 | - [Other languages/bindings](#other-languagesbindings) 47 | - [License](#license) 48 | 49 | High-performance Go database 50 | -------------------------------- 51 | 🏁 **High performance** on restricted devices, like IoT gateways, micro controllers, ECUs etc.\ 52 | 💚 **Resourceful** with minimal CPU, power and Memory usage for maximum flexibility and sustainability\ 53 | 🔗 **Relations:** object links / relationships are built-in\ 54 | 💻 **Multiplatform:** Linux, Windows, Android, iOS, macOS 55 | 56 | 🌱 **Scalable:** handling millions of objects resource-efficiently with ease\ 57 | 💐 **Queries:** filter data as needed, even across relations\ 58 | 🦮 **Statically typed:** compile time checks & optimizations\ 59 | 📃 **Automatic schema migrations:** no update scripts needed 60 | 61 | **And much more than just data persistence**\ 62 | 👥 **[ObjectBox Sync](https://objectbox.io/sync/):** keeps data in sync between devices and servers\ 63 | 🕒 **[ObjectBox TS](https://objectbox.io/time-series-database/):** time series extension for time based data 64 | 65 | 66 | Getting started: Go! 67 | --------------- 68 | To install ObjectBox, execute the following command in your project directory. 69 | You can have a look at [installation docs](https://golang.objectbox.io/install) for more details and further instructions. 70 | ```bash 71 | bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-go/main/install.sh) 72 | ``` 73 | 74 | To install [ObjectBox Sync](https://objectbox.io/sync/) variant of the library, pass `--sync` argument to the command above: 75 | 76 | ```bash 77 | bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-go/main/install.sh) --sync 78 | ``` 79 | 80 | You can run tests to validate your installation 81 | ```bash 82 | go test github.com/objectbox/objectbox-go/... 83 | ``` 84 | 85 | With the dependencies installed, you can start adding entities to your project: 86 | ```go 87 | //go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen 88 | ​ 89 | type Task struct { 90 | Id uint64 91 | Text string 92 | } 93 | ``` 94 | And run code generation in your project dir 95 | ```bash 96 | go generate ./... 97 | ``` 98 | This generates a few files in the same folder as the entity - remember to add those to version control (e. g. git). 99 | 100 | Once code generation finished successfully, you can start using ObjectBox: 101 | ```go 102 | obx := objectbox.NewBuilder().Model(ObjectBoxModel()).Build() 103 | box := BoxForTask(obx) // Generated function to provide a Box for Task objects 104 | id, _ := box.Put(&Task{ Text: "Buy milk" }) 105 | ``` 106 | 107 | See the [Getting started](https://golang.objectbox.io/getting-started) section of our docs for a more thorough intro. 108 | 109 | Also, please have a look at the [examples](examples) directory and for the API reference see 110 | [ObjectBox GoDocs](https://godoc.org/github.com/objectbox/objectbox-go/objectbox) - and the sources in this repo. 111 | 112 | Already using ObjectBox Database? 113 | --------------------------- 114 | 115 | **Your opinion matters to us!** Please fill in this 2-minute [Anonymous Feedback Form](https://forms.gle/LvVjN6jfFHuivxZX6). 116 | 117 | We believe, ObjectBox is super easy to use. We are on a mission to make developers’ lives better, by building developer tools that are intuitive and fun to code with. 118 | To do that, we want your feedback: what do you love? What's amiss? Where do you struggle in everyday app development? 119 | 120 | **We're looking forward to receiving your comments and requests:** 121 | 122 | - Add [GitHub issues](https://github.com/ObjectBox/objectbox-go/issues) 123 | - Upvote issues you find important by hitting the 👍/+1 reaction button 124 | - Drop us a line via [@ObjectBox_io](https://twitter.com/ObjectBox_io/) 125 | - ⭐ us, if you like what you see 126 | 127 | Thank you! 🙏 128 | 129 | Keep in touch: For general news on ObjectBox, [check our blog](https://objectbox.io/blog)! 130 | 131 | 132 | Upgrading to a newer version 133 | ---------------------------- 134 | When you want to update, please re-run the entire installation process to ensure all components are updated: 135 | 136 | * ObjectBox itself (objectbox/objectbox-go) 137 | * Dependencies (flatbuffers) 138 | * ObjectBox library (libobjectbox.so|dylib; objectbox.dll) 139 | * ObjectBox code generator 140 | 141 | This is important as diverging versions of any component might result in errors. 142 | 143 | The `install.sh` script can also be used for upgrading: 144 | ```bash 145 | bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-go/main/install.sh) 146 | ``` 147 | 148 | Afterwards, don't forget to re-run the code generation on your project 149 | ```bash 150 | go generate ./... 151 | ``` 152 | 153 | Other languages/bindings 154 | ------------------------ 155 | ObjectBox supports multiple platforms and languages: 156 | 157 | * [Java/Kotlin Database](https://github.com/objectbox/objectbox-java): runs on Android, desktop, and servers 158 | * [Swift Database](https://github.com/objectbox/objectbox-swift): build fast mobile apps for iOS (and macOS) 159 | * [Dart/Flutter Database](https://github.com/objectbox/objectbox-dart): cross-platform for mobile and desktop apps 160 | * [C and C++ Database](https://github.com/objectbox/objectbox-c): native speed with zero copy access to FlatBuffer objects 161 | 162 | 163 | License 164 | ------- 165 | Copyright 2018-2024 ObjectBox Ltd. All rights reserved. 166 | 167 | Licensed under the Apache License, Version 2.0 (the "License"); 168 | you may not use this file except in compliance with the License. 169 | You may obtain a copy of the License at 170 | 171 | http://www.apache.org/licenses/LICENSE-2.0 172 | 173 | Unless required by applicable law or agreed to in writing, software 174 | distributed under the License is distributed on an "AS IS" BASIS, 175 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 176 | See the License for the specific language governing permissions and 177 | limitations under the License. 178 | 179 | --------------------------------------------------------------------------------