Skip to main content

Writing Data to ZefDB

⚛ Atomic Changes ⚛

Any changes to ZefDB occur atomically. This makes it safe to deal with concurrent and parallel code across threads and compute nodes: you never have to worry about observing an intermediate or incomplete state.

📝 Describing Your Changes 📝

To allow these state changes to occur atomically, you do not mutate your domain objects in place, one change at a time.
Rather, Zef allows you to move away from this imperative style to a more declarative style: you describe all changes you would like to have performed as a data structure (a list of changes, aka change list).

This also makes it safe to change your DB state from different threads or compute nodes: all changes are sent to the transacting thread via a queue: the changes are taken from this queue one at a time and applied sequentially.

  • well defined sequence of events (linearizability)
  • you can cause changes from different threads: decoupling of the source of change from where the state change is executed
  • invariants (local and non-local) on all resulting states can be enforced (schema)

The structure of a change list: simply a Python list

my_changes = [  
change1,
change2,
change3,
]

each item in the change list is one of

  • an object
  • a triple
  • an extended command

🌳 Object Notation 🌳

The object notation focuses on ease of use. It may seem very natural if you are familiar to working with dictionaries, objects or structs: It provides a terse way to express your domain objects directly in a readable, tree-structured form.

Creating a new entity

ET.Movie()    # bare object: no internal fields  

One-to-One Relations

ET.Movie(  
title='Pulp Fiction',
year_of_release=1994,
)

one one relation of a given type going out from entity

One-to-Many Relations

ET.Movie(  
genre={
'drama',
'thriller',
'gangster',
}
)

multiple edges/relations of the same type going out from the same entity: represented as a set

Zef-Lists

When order is important: the same semantics as a list/array, but "flattened out" on the graph.

changes = [  

]

☘️ Semantic Triples Notation (Datalog)☘️

[  
(eiffel_tower, RT.located_in, cities.paris),
]

A triple is equivalent to a relation / arrow: (source, relation_type, target)

📆 Updating Existing Atoms 📆

fargo = ET.Movie('㏈-4398652364785625864735') # ref to existing entity  

fargo(
year_of_release=1996, # set this field
)

The minimum amount of changes to achieve the target state will be performed:

  • if the field does not exist, a new AE is create and the value is assigned
  • if the field exists and is an AE, then the new value is assigned
  • if the field exists as a relation to a value node, the previous relation is terminated and a new relation is created
(fargo, RT.directed_by, joel_cohen)   # triple notation  

Using the triple notation is always "additive": a new relation is always created, it does not overwrite or interfere with existing fields.
One can also think of it as "adding a fact to the database"

Differential Updates to One-to-Many Relations

fargo(  
actor=Add( # special wrapper to indicate intent
steve_buscemi,
peter_stormare,
)
)

bob(
on_bucket_list=Remove(
cities.cape_town,
cities.hotazel,
)
)

don't overwrite the existing set: just add or remove the relations

⚙️ Updates with Function Calls ⚙️

alice(  
bank_balance = Update(add[10*currency.usd])
)

this is the syntax if we have a reference to the source and want to operate on an attached field.

If we have reference to the AE we want to atomically update directly, we can use the ZefOp

bank_balance_ae | update[subtract[5*currency.usd]]  

Since it is a lazy value, it can be passed as an expression without doing anything at the definition site. It will be interpreted at by the transactor.

✍️ Assigning Values to Existing AEs ✍️

if you have a reference to an existing AE and you want to assign a new value:

[  
Assign(42, my_ae)
my_ae | assign[42],
my_ae << 42, # shorthand notation
]

⚰️ Terminating Atoms ⚰️

[  
-my_atom,
Terminate(my_atom)
]