HexaDb is a triple based graph data store created on RocksDb storage. It can be used to store, retrieve and query JSON documents. HexaDb does not require a schema. It also supports relational query through providing nested path or nesting level in any arbitrary JSON document.
HexaDb breaks JSON documents to RDF triples in the format of (S,P,O)
and creates six indices. All triples are queryable by those six indices
S, P, O, SP, PO, OS
Thus HexaDb.
$ docker-compose up
For more detailed documentation take a look at the wiki
You can also find example scenarios at hexadb-examples
Let's create a graph. First we will create two object's with nested JSON objects. Later, we will create another object that references these objects by id.
POST /api/store/app01
[
{
"id": "sensor:0",
"type": "sensor",
"name": "Entity sensor 0",
"temperature": 64.44,
"humidity": 14.65,
"pressure": 957.1,
"marker": {
"status": "running",
"red": 9.12,
"blue": 4.53,
"green": 9.85
}
},
{
"id": "sensor:1",
"type": "sensor",
"name": "Entity sensor 1",
"temperature": 65.86,
"humidity": 12.29,
"pressure": 945.19,
"marker": {
"status": "stopped",
"red": 9.95,
"blue": 7.16,
"green": 2.02
}
}
]
The top level objects in the POST call need to have an id field. The nested objects will be assigned id's based on the top level id. This POST call creates four different objectswith links. Nested objects are considered top level and can be queried indepently.
GET /api/store/app01/sensor:1
POST /api/store/app01/query
This query finds all object with type
of sensor
and with a temperature
property > 65
.
{
"filter": {
"type": {
"op": "eq",
"value": "sensor"
},
"temperature" : {
"op" : "gt",
"value" : 65,
}
}
}
Posting a nested object automatically creates relationships. In HexaDb all objects are considered to be a top-level object. New objects and relationships can be created with just another POST with id references to existing objects. This is similar to POST with inline objects. But the object does not need to be inline. An id reference to an existing object is good enough.
POST /api/store/app01
{
"id" : "room:0",
"name" : "Room Zero",
"type" : "room",
"description" : "Has two sensors",
"sensors" : [
{
"id" : "sensor:0"
},
{
"id" : "sensor:1"
}
]
}
This creates the following structure
We are trying to find rooms that have a sensor with a marker
object that has a property of green > 9
. This can be thought of as the following pattern search in the graph. To query multiple nesting levels with named paths, a #
separated list of path names are used. In this case it is sensors#marker
POST /api/store/app01/query
{
"filter": {
"type": {
"op": "eq",
"value": "room"
}
},
"outgoing": [
{
"path": "sensors#marker",
"target": {
"filter": {
"green": {
"op": "gt",
"value": 9
}
}
}
}
]
}
Here is the same query without specifying the explicit relationships. This is querying all outgoing objects in the nesting level of 3
. Notice that the path
is *
so it will match any objects in that vicinity.
POST /api/store/app01/query
{
"filter": {
"type": {
"op": "eq",
"value": "room"
}
},
"outgoing": [
{
"path": "*",
"level": 3,
"target": {
"filter": {
"green": {
"op": "gt",
"value": 9
}
}
}
}
]
}
Note that without specifying the path all the objects in the nesting level will be considered for a pattern match. For example this query may look like below
Similar to the outgoing queries it is also possible to query objects that are pointed to by other objects. E.q. in a parent->child
relatioship children can be found by incoming
query from the parent.
Incoming queries can also be done with nesting level with *
as path.
{
"filter": {
"type": {
"op": "eq",
"value": "sensor"
}
},
"incoming": [
{
"path": "sensors",
"target": {
"filter": {
"name": {
"op": "contains",
"value": "Room"
}
}
}
}
]
}
This is querying for the following pattern in the graph
PATCH /api/store/app01/json
A json-merge-patch style endpoint is available. Below is an example that changes the name of the object pointed to by sensor:0
and modifies the marker
relationship to contain a single object with no status
(deleted) and value of red
with 1.0
.
{
"id": "sensor:0",
"name": "Another name",
"marker": {
"status": null,
"red": 1.0
}
}
PATCH /api/store/app01/triple
Hexadb also supports patching at the triple level. The patches are performed with two separate add
and remove
sections. The remove
is performed before add
.
{
"remove": {
"id": "room:0",
"sensors": [
{
"id": "sensor:0"
}
]
},
"add": {
"id": "room:1",
"sensors": [
{
"id": "sensor:2",
"temperature" : 50,
"description" : "some other sensor"
}
]
}
}
The data store can be replicated to multiple instances using event hubs. The store supports the following environment variables to use for replication.
HEXASTORE_EVENTHUB_KEY
HEXASTORE_EVENTHUB_PARTITION_COUNT
HEXASTORE_EVENTHUB_NAME
The HEXASTORE_EVENTHUB_KEY
must contain EntityPath
. HEXASTORE_EVENTHUB_PARTITION_COUNT
denotes the number of partitions to send replication data to.
HEXASTORE_EVENTHUB_NAME
identifies the event hub as a name. If this is changed the event hub configuration will be treated as a new configuration with
no checkpoints. The checkpoints are used to remember the offset to which the database contains information. On restart the writes are read from the already
saved offset in the database.