objectbox-generator

module
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2020 License: GPL-3.0

README


Follow ObjectBox on Twitter

ObjectBox Generator

Current version: 0.9

ObjectBox is a superfast cross-platform object-oriented database. ObjectBox Generator takes over the burden of writing the C/C++ binding code for ObjectBox (more languages to be supported in the future). This greatly simplifies the model declaration and FlatBuffers serialization, allowing you to concentrate on the actual application logic.

All the generated code is header-only and compatible with the existing ObjectBox C-API so you can take advantage of the new features while incrementally porting your existing code.

Prerequisites/Installation

  1. You need to have ObjectBox-C library installed to use code generated by ObjectBox Generator in your project. Please follow the installation instructions.

  2. Install the objectbox-generator by downloading the latest binary for your OS from releases. If you want, add it to $PATH for convenience. Alternatively, instead of downloading, you can build the generator yourself by cloning this repo and running make. To build yourself, you need a recent Go version, CMake and a C++11 tool chain.

  3. Get a FlatBuffers library:

Getting started

ObjectBox Generator uses FlatBuffer schema file (.fbs) as its input. It stores the model information within a model JSON file known from other ObjectBox language bindings (objectbox-model.json) and generates code based on the selected language - C or C++.

Let’s have a look at a sample schema (tasklist.fbs) and how Generator helps us.

table Task {
    id: ulong;
    text: string;
    date_created: ulong;
    date_finished: ulong;
}
For C++ projects

Running objectbox-generator -cpp tasklist.fbs will generate C++ binding code for tasklist.fbs - we get the following files:

  • objectbox-model.h
  • objectbox-model.json
  • tasklist-cpp.obx.h

Note: you should add all these files to your source control (e.g. git), most importantly the objectbox-model.json which ensures compatibility with previous versions of your database after you make changes to the schema.

Now in your application, you can include the headers and start to work with your database. Consider the following main.cpp:

#include "objectbox-cpp.h"
#include "objectbox-model.h"
#include "tasklist-cpp.obx.h"

int main(int argc, char* args[]) {
    // create_obx_model() provided by objectbox-model.h
    // obx interface contents provided by objectbox-ext.h
    obx::Store store(create_obx_model());
    obx::Box<Task> box(store);

    obx_id id = box.put({.text = "Buy milk"});  // Create
    std::unique_ptr<Task> task = box.get(id);   // Read
    if (task) {
        task->text += " & some bread";
        box.put(*task);                         // Update
        box.remove(id);                         // Delete
    }
    return 0;
}

To compile, just link to the objectbox-c library, e.g. something like this should work: g++ main.cpp -I. -std=c++11 -lobjectbox. Note: the command snippet assumes you have objectbox-c library installed in a path recognized by your OS (e.g. /usr/local/lib/) and all the referenced headers are in the same folder as main.cpp.

For C projects

Running objectbox-generator -c tasklist.fbs will generate C binding code for tasklist.fbs - we get the following files:

  • objectbox-model.h
  • objectbox-model.json
  • tasklist.obx.h

Note: you should add all these files to your source control (e.g. git), most importantly the objectbox-model.json which ensures compatibility with previous versions of your database after you make changes to the schema.

Now in your application, you can include the headers and start to work with your database. Have a look at the following main.c showing one of the many ways you can work with objectbox-c and the generated code:

#include "objectbox-model.h"
#include "objectbox.h"
#include "tasklist.obx.h"

obx_err print_last_error() {
    printf("Unexpected error: %d %s\n", obx_last_error_code(), obx_last_error_message());
    return obx_last_error_code();
}

obx_id task_put(OBX_box* box, Task* task) {
    flatcc_builder_t builder;
    flatcc_builder_init(&builder);

    size_t size;
    void* buffer;

    // Note: Task_to_flatbuffer() is provided by the generated code
    obx_id id = 0;
    if (Task_to_flatbuffer(&builder, task, &buffer, &size)) {
        id = obx_box_put_object(box, buffer, size, OBXPutMode_PUT);  // returns 0 on error
    }

    flatcc_builder_clear(&builder);

    if (id == 0) {
        // TODO: won't be able to print the right error if it occurred in Task_to_flatbuffer(), i.e. outside objectbox
        print_last_error();
    } else {
        task->id = id;  // Note: we're updating the ID on new objects for convenience
    }

    return id;
}

Task* task_read(OBX_store* store, OBX_box* box, obx_id id) {
    OBX_txn* txn = NULL;

    // We need an explicit TX to read - read flatbuffers lifecycle is bound to the  open transaction.
    // The transaction can be closed safely after reading the object properties from flatbuffers.
    txn = obx_txn_read(store);
    if (!txn) {
        print_last_error();
        return NULL;
    }

    void* data;
    size_t size;
    int rc = obx_box_get(box, id, &data, &size);
    if (rc != OBX_SUCCESS) {
        // if (rc == OBX_NOT_FOUND); // No special treatment at the moment if not found
        obx_txn_close(txn);
        return NULL;
    }

    Task* result = Task_new_from_flatbuffer(data, size);
    obx_txn_close(txn);
    return result;
}

int main(int argc, char* args[]) {
    int rc = 0;
    OBX_store* store = NULL;
    OBX_box* box = NULL;
    Task* task = NULL;

    // Firstly, we need to create a model for our data and the store
    {
        OBX_model* model = create_obx_model();  // create_obx_model() provided by objectbox-model.h
        if (!model) goto handle_error;
        if (obx_model_error_code(model)) {
            printf("Model error: %d %s\n", obx_model_error_code(model), obx_model_error_message(model));
            obx_model_free(model);
            goto handle_error;
        }

        OBX_store_options* opt = obx_opt();
        obx_opt_model(opt, model);
        store = obx_store_open(opt);
        if (!store) goto handle_error;

        // obx_store_open() takes ownership of model and opt and frees them. We must not access them anymore.
    }

    box = obx_box(store, Task_ENTITY_ID);  // Note the generated "Task_ENTITY_ID"

    obx_id id = 0;

    {  // Create
        Task task = {.text = "Buy milk"};
        id = task_put(box, &task);
        if (!id) goto handle_error;
        printf("New task inserted with ID %d\n", id);
    }

    {  // Read
        task = task_read(store, box, id);
        if (!task) goto handle_error;
        printf("Task %d read with text: %s\n", id, task->text);
    }

    {  // Update
        const char* appendix = " & some bread";

        // updating a string property is a little more involved but nothing we can't manage with a little elbow grease
        size_t old_text_len = task->text ? strlen(task->text) : 0;
        char* new_text = (char*) malloc((old_text_len + strlen(appendix) + 1) * sizeof(char));

        if (task->text) {
            memcpy(new_text, task->text, old_text_len);

            // free the previously allocated memory, which would otherwise be lost when overwritten below
            free(task->text);
        }
        memcpy(new_text + old_text_len, appendix, strlen(appendix) + 1);
        task->text = new_text;
        printf("Updated task %d with a new text: %s\n", id, task->text);
    }

    // Delete
    if (obx_box_remove(box, id) != OBX_SUCCESS) goto handle_error;

free_resources:  // free any remaining allocated resources
    if (task) Task_free(&task); // Note: we must free the object allocated by Task_new_from_flatbuffer()
    if (store) obx_store_close(store);
    return rc;

handle_error:  // print error and clean up
    rc = print_last_error();
    if (rc <= 0) rc = 1;
    goto free_resources;
}

To compile, link to the objectbox-c library and flatcc-runtime library, e.g. something like this should work: gcc main.c -I. -lobjectbox -lflatccrt. Note: the command snippet assumes you have objectbox-c and flatccrt libraries installed in a path recognized by your OS (e.g. /usr/local/lib/) and all the referenced headers are in the same folder as main.c.

Annotations

The source FlatBuffer schema can contain some ObjectBox-specific annotations, declared as specially formatted comments to table and field FlatBuffer schema elements. Have a look at the following schema example showing of a few of the annotations and the various formats you can use.

/// This entity is not annotated and only serves as a relation target in this example
table Simple {
    id:ulong;
}

/// objectbox: name=AnnotatedEntity
table Annotated {
    /// Objectbox requires an ID property.
    /// It is recognized automatically if it has a right name ("id"), otherwise it must be annotated.
    /// objectbox:id
    identifier:ulong;

    /// objectbox:name="name",index=hash64
    fullName:string;

    /// objectbox:id-companion, date
    time:int64;

    /// objectbox:transient
    skippedField:[uint64];

    /// objectbox:link=Simple
    relId:ulong;
}
Annotation format

The simplified rules how annotation-specific comments are recognized:

  • Must be a comment immediately preceding an Entity or a Property (no empty lines between them).
  • The comment must start with three slashes so it's be picked up by FlatBuffer schema parser as "documentation".
  • Spaces between words inside the comment are skipped so you can use them for better readability, if you like. See e.g. Annotated, time.
  • The comment must start with the text objectbox: and is followed by one or more annotations, separatedy by commas.
  • Each annotation has a name and some annotations also support specifying a value (some even require a value, e.g. name). See e.g. Annotated, relId.
  • Value, if present, is added to the annotation by adding an equal sign and the actual value.
  • A value may additionally be surrounded by double quotes but it's not necessary. See e.g. fullName showing both variants.
Supported annotations

The following annotations are currently supported:

Entity annotations
  • name - specifies the name to use in the database if it's desired to be different than what the FlatBuffer schema "table" is called.
  • transient - this entity is skipped, no code is generated for it. Useful if you have custom FlatBuffer handling but still want to generate ObjectBox binding code for some parts of the same file.
  • uid - used to explicitly specify UID used with this entity; used when renaming entities. See Go documentation on schema changes which applies here as well.
Property annotations
  • date - tells ObjectBox the property is a timestamp, ObjectBox expects the value to be a timestamp since UNIX epoch, in milliseconds.
  • id - specifies this property is a unique identifier of the object - used for all CRUD operations.
  • id-companion - identifies a companion property, currently only supported on date properties in time-series databases.
  • index - creates a database index. This can improve performance when querying for that property. You can specify an index type as the annotation value:
    • not specified - automatically choose the index type based on the property type (hash for string, value for others).
    • value - uses property values to build the index. For string, this may require more storage than a hash-based index.
    • hash - uses a 32-bit hash of property value to build the index. Occasional collisions may occur which should not have any performance impact in practice (with normal value distribution). Usually a better choice than hash64, as it requires less storage.
    • hash64 - uses long hash of property values to build the index. Requires more storage than hash and thus should not be the first choice in most cases.
  • link - declares the field as a relation ID, linking to another Entity which must be specified as a value of this annotation.
  • name - specifies the name to use in the database if it's desired to be different than what the FlatBuffer schema "field" is called.
  • transient - this property is skipped, no code is generated for it. Useful if you have custom FlatBuffer handling but still want to generate ObjectBox binding code for the entity.
  • uid - used to explicitly specify UID used with this property; used when renaming properties. See Go documentation on schema changes which applies here as well.
  • unique - set to enforce that values are unique before an entity is inserted/updated. A put operation will abort and return an error if the unique constraint is violated.

License

Copyright (C) 2020 ObjectBox Ltd. All rights reserved.
https://objectbox.io
This file is part of ObjectBox Generator.

ObjectBox Generator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ObjectBox Generator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ObjectBox Generator.  If not, see <http://www.gnu.org/licenses/>.

Note: GPL only applies to the Generator itself and not to generated code. You can regard generated code as "your code", and we impose no limitation on distributing it. And, just to clarify: as the Generator does not include any warranty, there can be no warranty for the code it generates.

Do you ♥️ using ObjectBox?

We want to hear about your project! It will - literally - take just a minute, but help us a lot. Thank you!​ 🙏​

Directories

Path Synopsis
cmd
objectbox-generator
Generates objectbox related code by reading models defined as Flatbuffers schemas (.fbs files).
Generates objectbox related code by reading models defined as Flatbuffers schemas (.fbs files).
internal
generator
Package generator provides tools to generate ObjectBox entity bindings between GO structs & ObjectBox schema
Package generator provides tools to generate ObjectBox entity bindings between GO structs & ObjectBox schema
test

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL