├── 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
25 | #include
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
25 | #include
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
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
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
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
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<...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
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 | 
2 |
3 |
4 | Getting Started •
5 | Documentation •
6 | Example Apps •
7 | Issues
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
--------------------------------------------------------------------------------