Home

A self describing graph schema

Updated:
Created:

2 years ago Christoph Pingel and myself designed a self describing graph schema.

This page explains a self describing schema for graphs. It has some recursions. See below for an explanation.

Original

I keep the original here, because of IP reasons. There is also a “zeitsiegel” on it :-)

Cypher

This is the cypher we created back then to write down the schema

create (_sem_label:`Sem_Label` {`description`:"the meta label to label labels", `techname`:"Sem_Label"})
create (_description:`Sem_Property` {`description`:"longer description", `scalartype`:"string", `techname`:"description"})
create (_techname:`Sem_Property` {`description`:"internal scalar representation", `scalartype`:"string", `techname`:"techname"})
create (_sem_n_prop:`Sem_Relation` {`description`:"Sem_Label --Sem_N_PROP-> Sem_Property", `techname`:"Sem_N_PROP"})
create (_sem_r_prop:`Sem_Relation` {`description`:"Sem_Relation --Sem_R_PROP-> Sem_Property", `techname`:"Sem_R_PROP"})
create (_arity:`Sem_Property` {`description`:"How often can there be the element", `scalartype`:"string", `techname`:"arity"})
create (_sem_relation:`Sem_Label` {`description`:"used to define a type of relation", `techname`:"Sem_Relation"})
create (_sem_property:`Sem_Label` {`description`:"A description of  property of semantic meta object", `techname`:"Sem_Property"})
create (_scalartype:`Sem_Property` {`description`:"Von welchem Typ ist der Wert", `scalartype`:"string", `techname`:"scalartype"})
create (_name:`Sem_Property` {`description`:"full name of  thing",  `scalartype`:"string", `techname`:"name"})
create (_person:`Sem_Label` {`description`:"a human", `techname`:"Person"})
create (_firstname:`Sem_Property` {`description`:"first name of a person", `scalartype`:"string", `techname`:"firstname"})
create (_lastname:`Sem_Property` {`description`:"last name of a person", `scalartype`:"string", `techname`:"lastname"})
create (_likes:`Sem_Relation` {`description`:"xoxoxo", `techname`:"LIKES"})
create (_bob:`Person` {`name`:"Bob"})
create (_alice:`Person` {`firstname`:"Alice", `lastname`:"Alison", `name`:"Alice Alison"})


create (_sem_label)-[:`Sem_N_PROP` {`arity`:"1"}]->(_description)
create (_sem_label)-[:`Sem_N_PROP` {`arity`:"1"}]->(_techname)
create (_sem_n_prop)-[:`Sem_R_PROP` {`arity`:1}]->(_arity)
create (_sem_r_prop)-[:`Sem_N_PROP` {`arity`:1}]->(_arity)
create (_sem_relation)-[:`Sem_N_PROP` {`arity`:"1"}]->(_techname)
create (_sem_relation)-[:`Sem_N_PROP` {`arity`:"1"}]->(_description)
create (_sem_property)-[:`Sem_N_PROP` {`arity`:1}]->(_scalartype)
create (_sem_property)-[:`Sem_N_PROP` {`arity`:1}]->(_description)
create (_sem_property)-[:`Sem_N_PROP` {`arity`:"1"}]->(_techname)
create (_person)-[:`Sem_N_PROP` {`arity`:"?"}]->(_lastname)
create (_person)-[:`Sem_N_PROP` {`arity`:"?"}]->(_firstname)
create (_person)-[:`Sem_N_PROP` {`arity`:"1"}]->(_name)
create (_bob)-[:`LIKES`]->(_alice)
create (_alice)-[:`LIKES`]->(_bob)
;

Neo4j Browser view

Visualized, it becomes this:

Explanation

What is the purpose of our undertaking? When we represent something in the graph, we need a model in order to do it in a meaningful way. This is also necessary for editors, so that you can get nice descriptions of properties of a matching selection of allowed relationship types to choose from.

Now great, we can edit the representation. But how do we modify the model, or extend it? Do we need another editor for that? Or can we use the same tool, and have a model for the model? A metamodel?

And while we are at it: how do we edit the metamodel? Well, if only the metamodel would be self describing…

The diagram

First, a newer visualization that contains the same ideas as the original, but with a bit more information in it:

What it means

We go from a very simple example of something we would to like to represent in our graph (Instance), to a slightly higher up modeling level (Model) and from there to the highest level (Meta)

Instance

We have Alice and Bob, and they like it each other. Great. They both have a first name, a last name, we give them a name to identify them in the graph, and of course we need to draw the two relationships / edges between them, both with the type “LIKES”. These are the yellow boxes at the top.

Model

Now, in order to edit this, we need several pieces of information:

  1. Label: Person: This describes a label for a person (like Alice, or Bob). The label itself has a name and a description. A label is the same as a label in neo4j.
  2. Property: firstname: This describes the property firstname, the given name. It uses a string field.
  3. Property: lastname: The family name, also a string
  4. Property: name: The identifier of the node. This should be unique. This also uses a string field.
  5. PROP: These edges show that the label Person uses the three properties firstname, lastname and name. used by something else, which is explained in a second.
  6. Relation: LIKES: This is the type of relationship used between Alice and Bob. The arities are explained further down. This is the type of the relationship in neo4j.
  7. SOURCE: This shows that the source of such a relationship can be a Person.
  8. TARGET: Also the target has to have the label Person.

Using this model, we could easily model a whole world of people and their relationships. Hurray. But right now Alice couldn’t like a cat. Why? Because we would need to add a label Cat for that, and also would need a TARGET relationship from LIKES to Cat.

How could we add or edit labels, properties and relationships?

Meta

This is where the meta layer comes in. It describes the model as a graph, and as it uses the same approach we have been using for modeling, you could call it the metamodel. This means, that once you have an editor that can make use of the meta level, it can not only change the Instance level, but would also work on the model level and the meta level itself. Back then, we could some real headache from thinking about it, so don’t expect to understand it in the first minute.

  1. Label: Label: This label is used to label other label nodes ( e.g. Person), but because it is itself a label, it is also used for itself (fun, isn’t it?). It uses two properties to describe itself. We already know name.
  2. Property: description: This property contains a longer description of something.
  3. Label: Property: A property like description has this label, to tell us, that it the meta node for a property, as opposed to e.g. being a Person. The property also uses name and description, but it also uses the more technical field. Nodes that carry this label are colored green.
  4. Property: field: This is just another property containing a hint of what scalar the value uses, so that e.g. a proper widget could be displayed, like a date picker, or a point selector on a map.
  5. Relation: Prop: The relationship PROP is of label Relation. It connects a Label node to a Property node. This is how we link the properties to their corresponding labels.
  6. Label: Relation: This is the meta label for relationships. With this we describe what we can but as a type on a relation / edge. So the type of relationship is a node that carries the label Relation, and it is colored blue.
  7. Relation: SOURCE: A relationship that describes what label a possible starting point for a relationship would be. Please note that SOURCE goes from the label to the relation, in order to keep directions sorted.
  8. Relation: TARGET: This describes what a possible target label for a relationship is.
  9. Property: sourcearity: This describes, how many outgoing edges of this type the source node can have. E.g. a car can only have one owner, or it is custom that a Person likes only one other Person in a romantic way.
  10. Property: targetarity: The same as sourcearity, but for incoming edges.

Now, as you can see, the metamodel uses the mechanic it describes to describe itself. The only thing that needs to be hardcoded are the names of the three top level labels: Label, Property and Relation. If an editor knows these, it has a starting point from where it can collect the whole metamodel and turn it into a fully fledged ontology.

Updated cypher

Here is the cypher that matches the explanation above

CREATE
(label:Label              {name: 'Label',
                           description: 'Defines a Label',
                           graph: 'metaschema'}),

(relation:Label           {name: 'Relation',
                           description: 'Defines a Relation type',
                           graph: 'metaschema'}),

(property:Label            {name: 'Property',
                           description: 'Defines a Property',
                           graph: 'metaschema'}),

(source:Relation          {name: 'SOURCE',
                           description: 'Start labels of Relation',
                           sourcearity: '*',
                           targetarity: '*',
                           graph: 'metaschema'}),

(target:Relation          {name: 'TARGET',
                           description: 'End labels of relation',
                           sourcearity: '*',
                           targetarity: '*',
                           graph: 'metaschema'}),

(prop:Relation            {name: 'PROP',
                           description: 'Label has Property',
                           sourcearity: '*',
                           targetarity: '*',
                           graph: 'metaschema'}),

(name:Property            {name: 'name',
                           description: 'name of a node',
                           widget: 'string',
                           arity: '1',
                           graph: 'metaschema'}),

(sourcearity:Property     {name: 'sourcearity',
                           description: 'How many relations of this type can the source have',
                           arity: '1',
                           widget: 'string',
                           graph: 'metaschema'}),

(targetarity:Property     {name: 'targetarity',
                           description: 'How many relations of this type can the target have',
                           arity: '1',
                           widget: 'string',
                           graph: 'metaschema'}),

(widget:Property          {name: 'widget',
                           description: 'What widget to use for this property (implies scalar)',
                           widget: 'string',
                           graph: 'metaschema'}),

(description:Property     {name: 'description',
                           description: 'What does it mean?',
                           arity: '?',
                           widget: 'string',
                           graph: 'metaschema'}),

(arity:Property           {name: 'arity',
                           description: 'How many values (regex style)',
                           widget: 'string',
                           arity: '?',
                           graph: 'metaschema'}),

(person:Label             {name: 'Person',
                           description: 'A living human',
                           graph: 'metaschema'}),

(firstname:Property       {name: 'firstname',
                           description: 'Given name',
                           widget: 'string',
                           graph: 'metaschema'}),

(lastname:Property        {name: 'lastname',
                           description: 'Family name',
                           widget: 'string',
                           graph: 'metaschema'}),

(likes:Relation           {name: 'LIKES',
                           description: 'xoxoxo',
                           sourcearity: '*',
                           targetarity: '*',
                           graph: 'metaschema'}),

(alice:Person             {name: 'alice',
                           firstname: 'Alice',
                           lastname: 'Alison',
                           graph: 'metaschema'}),

(bob:Person               {name: 'bob',
                           firstname: 'Bob',
                           lastname: 'Bobson',
                           graph: 'metaschema'}),

(label)-[:PROP]->(name),
(relation)-[:PROP]->(name),
(property)-[:PROP]->(name),
(label)-[:SOURCE]->(prop),
(label)-[:PROP]->(description),
(label)-[:SOURCE]->(source),
(target)-[:TARGET]->(label),
(source)-[:TARGET]->(relation),
(relation)-[:SOURCE]->(target),
(relation)-[:PROP]->(description),
(property)-[:PROP]->(arity),
(prop)-[:TARGET]->(property),
(property)-[:PROP]->(description),
(relation)-[:PROP]->(sourcearity),
(relation)-[:PROP]->(targetarity),
(property)-[:PROP]->(widget),

(person)-[:PROP]->(firstname),
(person)-[:PROP]->(lastname),
(likes)-[:SOURCE]->(person),
(person)-[:TARGET]->(likes),
(person)-[:PROP]->(name),

(alice)-[:LIKES]->(bob),
(bob)-[:LIKES]->(alice);

match (n {graph:'metaschema'}) return *