datomic_gen_server v2.0.1 DatomicGenServer.EntityMap

DatomicGenServer.EntityMap is a data structure designed to store the results of Datomic queries and transactions in a map of maps or structs. The keys of the EntityMap are by default Datomic entity IDs, but you may also index the map using other attributes as keys.

An EntityMap may be created from data tuples, records, or rows. A DataTuple is a generalization of Datom which contains e, a, v, and added fields, where v may be a scalar value or a collection. A record in this context is simply a map of attributes to values. A row is a list of attribute values, which must be accompanied by a “header row” containing a list of attribute keys in the same list position as their corresponding attribute values — i.e., the first key in the header is the attribute key for the first value in each of the rows, etc.

The main functions for creting an EntityMap are new (to create from data tuples), from_records, from_rows, and from_transaction (to create from a Datomic transaction). An EntityMap may be updated using update (from data tuples), update_from_records, update_from_rows, and update_from_transaction. An entity may be retrieved using get, and a single attribute from an entity using get_attr.

When creating an EntityMap you may specify the attribute key to index by and the struct into which to place attribute values (along with a translation table for mapping the keys in the incoming data to any fields in the struct that might have different names). You must also specify any cardinality many attributes; these values are contained in sets: If a data tuple contains a scalar value for a cardinality many attribute, then that value is added to the set of values for that attribute (or removed, in the case of retraction). If a data tuple contains a collection as a value for a cardinality many attribute, the values are added to or removed from the set of values for that attribute.

When working with records and rows, the behavior is different: The value for a cardinality many attribute in a row or record should always be a collection, and will always replace any pre-existing set of attributes. In other words, using records and rows always assumes that a single record or row contains the entire accumulated value for an attribute. There are no separate addition and retraction operations for records and rows; any attribute values in a record or row replace prior values. If a record or row does not contain an entry for a particular attribute, that attribute is left in its prior state.

Empty collections and nil are “magic values” with a special behavior. When working with data tuples, either adding or retracting nil from a scalar attribute deletes the key for that attribute from the attribute map. Adding or retracting an empty collection (list or set) from a cardinality many attribute resets that value to the empty set. If you want to completely replace a set of values for a cardinality many attribute with a different set, you would first retract nil for that attribute and then add the new attribute values. Note that nil and the empty collection always override any other values provided for the same operation (add or retract) for that attribute in a group of data tuples. In other words, if you retract a particular value and also retract nil in the same collection of data tuples, the nil wins; the same would go for adding nil along with other values. (You can, however, retract nil and at the same time add new values.)

When working with records and rows, if you supply nil or an empty collection as a value for an attribute, that value is removed from the attribute map (or set to the empty set if the attribute is cardinality many).

Note that internally, EntityMap uses an empty tuple as a null value marker. You should not use empty tuples in attribute values.

Empty entities — entities with no attribute values other than empty collections and the entity ID — are removed from the entity map.

Aggregation

You can specify a struct to carry attribute values, along with a translation from the attribute keys in the raw data to the names of the fields in the struct. Note that it is possible for an EntityMap to contain entities of different types, whereas an aggregator will narrow the EntityMap to entities of a single type — attribute maps that cannot be aggregated into the struct will not appear in the aggregated map. Note, however, that to be able to be aggregated into a struct, an attribute map only needs to share one key with that struct — any attributes that the map has that are not in the struct are discarded, and any fields of the struct that are not present in the attribute map are set to their default values. Note also that if you supply a translation table for attribute keys to struct fields, if an attribute map already has a key with the same name as a struct field, that value will still be mapped into the struct — i.e., if you provide a translation table that maps the attribute key :identifier to :id, then any attribute values with the key :identifier will be put into the struct’s id field—but so will any attribute values with the key :id.

Since any entities failing aggregation aren’t included in the aggregated map, you can use aggregators to filter an EntityMap to contain just those entities that you want. (See e.g., the aggregate_by function, which returns a new EntityMap containing the original data aggregated in a new way.) This also allows you to create multiple EntityMaps from a single original EntityMap, each of which has an aggregated map that references only certain entities in the data.

Internally, the EntityMap still contains all its original data, so if you re-aggregate an already aggregated map, the new aggregation is applied to all the data used to construct the original map, and not just to the data accessible in the already-aggregated map.

Indexing

You can choose an attribute that will supply the keys for the EntityMap. If the raw data for an entity does not contain a value for that attribute, that entity will not appear in the indexed map.

If you are indexing an aggregated map, the possible keys are the names of the struct fields, not the names of the raw attributes those fields have been translated from.

Summary

Functions

Returns an EntityMap containing the same underlying data as the supplied EntityMap, but with the entity attributes contained in the given struct. The optional third argument is an atom representing the name of one of the fields in the struct, whose values will be the keys in the returned EntityMap

Returns an EntityMap which is the supplied EntityMap with the entry for a specific index key deleted. If the EntityMap is not indexed, the entity ID is used. If the key does not exist, the EntityMap is returned unchanged

Returns true if two EntityMaps contain equal aggregated data maps as well as the same underlying data

Creates a new EntityMap from a list of records. A record is a map of attribute names to values

Creates a new EntityMap from a list of rows. A row is a simple list of attribute values. The header (second parameter) supplies a list of the attribute names for these values

Creates a new EntityMap from the datoms in a DatomicTransaction. These and the options are passed to the new function (see the documentation on that function)

Returns the value for a specific index key. If the key is not present, returns the default value (or nil if there is no default value)

Returns the value for a specific index key and attribute key. If either key is not present in the EntityMap the default value is returned (or nil if no default value is supplied)

Returns a boolean indicating whether a given index key exists in the supplied EntityMap

Returns a new EntityMap whose keys are values of a certain attribute or struct field rather than the entity IDs

Returns all index keys from the map, in a list

Creates a new EntityMap from a list of DataTuples. An EntityMap acts as a map of an entity id (or attribute value) to a map of an entity’s attributes

Returns an EntityMap with the given entity added. The supplied entity must be in the form of a map of attributes to values, including one attribute that specifies the entity ID (i.e., a value that would appear in the e: field of a Datom or DataTuple)

When successful, returns an :ok tuple containing an EntityMap with an updated value for a given entity’s attribute, where the entity is specified by its index key. If either the index or attribute key does not exist, an :error tuple is returned

A utility function that accepts a map and a translation table, and returns a new map whose keys have been renamed according to the translation table

Returns an EntityMap created from an existing EntityMap and a list of data tuples (both retractions and additions)

Returns an EntityMap created from an existing EntityMap and a list of records. A record is a map of attribute names to values. One of those attribute keys must point to the entity primary key — i.e., the same value that would appear in the e value of a corresponding datom or DataTuple. This attribute should be passed to the function as the third argument

Returns an EntityMap created from an existing EntityMap and a list of rows. A row is a simple list of attribute values. The header (third parameter) supplies a list of the attribute names for these values

Returns an EntityMap created from an existing EntityMap and the datoms in a DatomicTransaction. These are passed to the update function (see the documentation on that function)

Returns all entities from the EntityMap, in a list

Types

aggregate :: {module, map}
entity_map_option ::
  {atom, term} |
  {atom, MapSet.t} |
  {atom, aggregate}
t :: %DatomicGenServer.EntityMap{aggregate_field_to_raw_attribute: map, aggregator: (DataTuple.t -> term), cardinality_many: MapSet.t, index_by: term, inner_map: map, raw_data: map}

Functions

aggregate_by(entity_map, aggregate, new_index \\ nil)

Specs

aggregate_by(EntityMap.t, aggregate, term) :: EntityMap.t

Returns an EntityMap containing the same underlying data as the supplied EntityMap, but with the entity attributes contained in the given struct. The optional third argument is an atom representing the name of one of the fields in the struct, whose values will be the keys in the returned EntityMap.

The second argument, an “aggregate”, is a pair whose first element is a module (i.e., the name of a struct), and whose second element is a map for translating the keys of the underlying un-aggregated attributes into fields of the struct. The translation map only requires entries for fields that need to be translated; fields having the same name as the underlying attribute keys do not need to be in the translation table.

By default the returned EntityMap uses the same index as the supplied EntityMap; if you do not supply an index key, be sure the existing index key is a field in the new struct as well.

Example

d1 = %Datom{e: 0, a: :name, v: "Bill Smith", tx: 0, added: true}
d2 = %Datom{e: 0, a: :age, v: 32, tx: 0, added: true}
d3 = %Datom{e: 0, a: :identifier, v: :bill_smith, tx: 0, added: true}
d4 = %Datom{e: 1, a: :name, v: "Karina Jones", tx: 0, added: true}
d5 = %Datom{e: 1, a: :age, v: 64, tx: 0, added: true}
d6 = %Datom{e: 1, a: :identifier, v: :karina_jones, tx: 0, added: true}

result_struct = {TestPerson, %{identifier: :id, name: :names}}
entity_map = EntityMap.new([d1, d2, d3, d4, d5, d6], 
                cardinality_many: :name, index_by: :id, aggregate_into: result_struct)

new_result_struct = {TestPerson2, %{identifier: :id}}
re_aggregated = EntityMap.aggregate_by(entity_map, new_result_struct)

EntityMap.get(re_aggregated, :karina_jones)
  => %TestPerson2{age: 64, id: :karina_jones}
delete(entity_map, index_key)

Specs

delete(map, term) :: EntityMap.t

Returns an EntityMap which is the supplied EntityMap with the entry for a specific index key deleted. If the EntityMap is not indexed, the entity ID is used. If the key does not exist, the EntityMap is returned unchanged.

Example

d1 = %Datom{e: 0, a: :attr1, v: :value, tx: 0, added: true}
d2 = %Datom{e: 0, a: :attr2, v: :value2, tx: 0, added: true}
d3 = %Datom{e: 1, a: :attr2, v: :value3, tx: 0, added: true}
d4 = %Datom{e: 1, a: :attr3, v: :value2, tx: 0, added: true}

entity_map = EntityMap.new([d1, d2, d3, d4])

result = EntityMap.delete(entity_map, 0)

EntityMap.get(result, 0)
  => nil
e_key()

Specs

e_key :: :"datom/e"
equal?(entity_map1, entity_map2)

Specs

equal?(EntityMap.t, EntityMap.t) :: boolean

Returns true if two EntityMaps contain equal aggregated data maps as well as the same underlying data.

Example

d1 = %Datom{e: 0, a: :attr1, v: :value, tx: 0, added: true}
d2 = %Datom{e: 0, a: :attr2, v: :value2, tx: 0, added: true}
d3 = %Datom{e: 1, a: :attr2, v: :value3, tx: 0, added: true}
d4 = %Datom{e: 1, a: :attr3, v: :value2, tx: 0, added: false}

new_map = EntityMap.new([d1, d2, d3, d4])
new_map2 = EntityMap.new([d1, d2, d3, d4])

EntityMap.equal?(new_map, new_map2)
  => true
from_records(record_maps_to_add, primary_key, options \\ [])

Specs

from_records([map] | MapSet.t, term, [entity_map_option]) :: EntityMap.t

Creates a new EntityMap from a list of records. A record is a map of attribute names to values.

One of those attribute keys must point to the entity’s primary key — i.e., the same value that would appearin the e value of a corresponding datom or DataTuple. This key should be supplied as the second parameter to the function. By default, if the EntityMap does not aggregate the attributes into a struct, the attribute map will contain an extra field :”datom/e” whose value is the entity ID.

Note that the incoming records may include data for multiple different types of entities.

The following options are supported:

:cardinality_many - the name or names of attribute keys that correspond to cardinality/many attributes. If you have such attributes in your data, this option is required. The value for this option may be a single value, a list, or a set. The name should be the name of the attribute on the incoming record, irrespective of any aggregation. The value for a cardinality many attribute on an incoming record should be a set or a list.

:aggregate_into - this should be a pair, the first element of which is a module (i.e., the struct you wish to use to aggregate results) and the second of which is a map from keys in the record to fields of the struct. It is not necessary to map keys that have the same name as fields in the struct, but only keys that need to be translated. The aggregator is stored with the entity map; it is assumed that all the DataTuples or records that you will be adding or removing later have the same relevant attributes and so will be aggregated the same way.

:index_by - if you wish to use something other than the entity ID as the key for the EntityMap, specify the attribute name here. If you are aggregating the map into a struct, this should be the name of the field in the struct rather than the name of the attribute key in the record used to construct the EntityMap.

Example

  d1 = %{eid: 1, unique_name: :bill_smith, name: "Bill Smith", age: 32}
  d2 = %{eid: 2, unique_name: :karina_jones, name: "Karina Jones", age: 64}

  result_struct = {TestPerson, %{unique_name: :id, name: :names}}

  entity_map = EntityMap.from_records([d1, d2], :eid,  
                cardinality_many: [:name], 
                index_by: :id, 
                aggregate_into: result_struct)

  EntityMap.get(entity_map, :karina_jones)
    => %TestPerson{age: 64, id: :karina_jones, names: #MapSet<["Karina Jones"]>}
from_rows(rows_to_add, header, primary_key, options \\ [])

Specs

from_rows([list] | MapSet.t, list, term, [entity_map_option]) :: EntityMap.t

Creates a new EntityMap from a list of rows. A row is a simple list of attribute values. The header (second parameter) supplies a list of the attribute names for these values.

One of those attribute keys must point to the entity’s primary key — i.e., the same value that would appearin the e value of a corresponding datom or DataTuple. This key should be supplied as the third parameter to the function. By default, if the EntityMap does not aggregate the attributes into a struct, the attribute map will contain an extra field :”datom/e” whose value is the entity ID.

Note that the incoming records may include data for multiple different types of entities.

The following options are supported:

:cardinality_many - the name or names of attribute keys that correspond to cardinality/many attributes. If you have such attributes in your data, this option is required. The value for this option may be a single value, a list, or a set. The name should be the name of the attribute on the incoming row header, irrespective of any aggregation. The value for a cardinality many attribute on an incoming row should be a set or a list.

:aggregate_into - this should be a pair, the first element of which is a module (i.e., the struct you wish to use to aggregate results) and the second of which is a map from keys in the record to fields of the struct. It is not necessary to map keys that have the same name as fields in the struct, but only keys that need to be translated. The aggregator is stored with the entity map; it is assumed that all the DataTuples or records that you will be adding or removing later have the same relevant attributes and so will be aggregated the same way.

:index_by - if you wish to use something other than the entity ID as the key for the EntityMap, specify the attribute name here. If you are aggregating the map into a struct, this should be the name of the field in the struct rather than the name of the attribute key in the header.

Example

  header = [:eid, :unique_name, :name, :age]

  d1 = [1, :bill_smith, "Bill Smith", 32]
  d2 = [2, :karina_jones, "Karina Jones", 64]

  result_struct = {TestPerson, %{unique_name: :id, name: :names}}

  entity_map = EntityMap.from_rows([d1, d2], header, :eid,  
                cardinality_many: [:name], 
                index_by: :id, 
                aggregate_into: result_struct)

  EntityMap.get(entity_map, :karina_jones)
    => %TestPerson{age: 64, id: :karina_jones, names: #MapSet<["Karina Jones"]>}
from_transaction(transaction, options \\ [])

Specs

from_transaction(DatomicTransaction.t, [entity_map_option]) :: EntityMap.t

Creates a new EntityMap from the datoms in a DatomicTransaction. These and the options are passed to the new function (see the documentation on that function).

Example

d1 = %Datom{e: 0, a: :name, v: "Bill Smith", tx: 0, added: true}
d2 = %Datom{e: 0, a: :age, v: 32, tx: 0, added: true}
d3 = %Datom{e: 0, a: :identifier, v: :bill_smith, tx: 0, added: true}
d5 = %Datom{e: 3, a: :name, v: "Hartley Stewart", tx: 0, added: false}
d6 = %Datom{e: 3, a: :age, v: 44, tx: 0, added: false}
d7 = %Datom{e: 3, a: :identifier, v: :hartley_stewart, tx: 0, added: false}

transaction = %DatomicTransaction{
  basis_t_before: 1000, 
  basis_t_after: 1001, 
  added_datoms: [d1, d2, d3], 
  retracted_datoms: [d5, d6, d7], 
  tempids: %{-1432323 => 64}
}

result_struct = {TestPerson, %{identifier: :id, name: :names}}

result = EntityMap.from_transaction(transaction, 
          cardinality_many: [:name], index_by: :id, aggregate_into: result_struct)

EntityMap.get_attr(result, :bill_smith, :age)
  => 32
get(entity_map, index_key, default \\ nil)

Specs

get(EntityMap.t, term, term) :: term

Returns the value for a specific index key. If the key is not present, returns the default value (or nil if there is no default value).

If the EntityMap is not indexed, the supplied key should be an entity ID.

Example

d1 = %Datom{e: 0, a: :attr1, v: :value, tx: 0, added: true}
d2 = %Datom{e: 0, a: :attr2, v: :value2, tx: 0, added: true}

entity_map = EntityMap.new([d1, d2])

EntityMap.get(entity_map, 0)
  => %{attr1: :value, attr2: :value2, "datom/e": 0}
get_attr(entity_map, index_key, attr_key, default \\ nil)

Specs

get_attr(EntityMap.t, term, term, term) :: term

Returns the value for a specific index key and attribute key. If either key is not present in the EntityMap the default value is returned (or nil if no default value is supplied).

If the EntityMap is not indexed, the supplied key should be an entity ID. If the EntityMap is aggregated, the attribute key is the name of the field in the aggregated struct; the attribute names in the raw data are not referenced.

Example

d1 = %Datom{e: 0, a: :attr1, v: :value, tx: 0, added: true}
d2 = %Datom{e: 0, a: :attr2, v: :value2, tx: 0, added: true}

entity_map = EntityMap.new([d1, d2])

EntityMap.get_attr(entity_map, 0, :attr2)
  => :value2
has_key?(entity_map, index_key)

Specs

has_key?(EntityMap.t, term) :: boolean

Returns a boolean indicating whether a given index key exists in the supplied EntityMap.

Example

d1 = %Datom{e: 0, a: :attr1, v: :value, tx: 0, added: true}
d2 = %Datom{e: 0, a: :attr2, v: :value2, tx: 0, added: true}

entity_map = EntityMap.new([d1, d2])

EntityMap.has_key?(entity_map, 1)
  => false
index_by(entity_map, attribute)

Specs

index_by(EntityMap.t, term) :: EntityMap.t

Returns a new EntityMap whose keys are values of a certain attribute or struct field rather than the entity IDs.

If a given entity does not have a value for the index attribute, that entity will not be accessible in the indexed EntityMap. This function is applied post-aggregation, so you can use the name of a field in your struct. If there is a field in the underlying data that is not a field on the aggregating struct, you cannot use it as an index.

This function also assumes that the value you are indexing on is unique; otherwise, you will lose entities if more than one has the same attribute.

Example

d1 = %Datom{e: 0, a: :name, v: "Bill Smith", tx: 0, added: true}
d2 = %Datom{e: 0, a: :age, v: 32, tx: 0, added: true}
d3 = %Datom{e: 0, a: :identifier, v: :bill_smith, tx: 0, added: true}
d4 = %Datom{e: 1, a: :name, v: "Karina Jones", tx: 0, added: true}
d5 = %Datom{e: 1, a: :age, v: 64, tx: 0, added: true}
d6 = %Datom{e: 1, a: :identifier, v: :karina_jones, tx: 0, added: true}

result_struct = {TestPerson, %{identifier: :id, name: :names}}
entity_map = EntityMap.new([d1, d2, d3, d4, d5, d6], 
                cardinality_many: :name, index_by: :id, aggregate_into: result_struct)

re_indexed = EntityMap.index_by(entity_map, :age)

EntityMap.get(re_indexed, 64)
  => %TestPerson{age: 64, id: :karina_jones, names: #MapSet<["Karina Jones"]>}
keys(entity_map)

Specs

keys(EntityMap.t) :: [term]

Returns all index keys from the map, in a list.

Example

d1 = %Datom{e: 0, a: :name, v: "Bill Smith", tx: 0, added: true}
d2 = %Datom{e: 0, a: :age, v: 32, tx: 0, added: true}
d3 = %Datom{e: 0, a: :identifier, v: :bill_smith, tx: 0, added: true}
d4 = %Datom{e: 1, a: :name, v: "Karina Jones", tx: 0, added: true}
d5 = %Datom{e: 1, a: :age, v: 64, tx: 0, added: true}
d6 = %Datom{e: 1, a: :identifier, v: :karina_jones, tx: 0, added: true}

entity_map = EntityMap.new([d1, d2, d3, d4, d5, d6], index_by: :identifier)

EntityMap.keys(entity_map)
  => [:bill_smith, :karina_jones]
new(data_tuples_to_add \\ [], options \\ [])

Creates a new EntityMap from a list of DataTuples. An EntityMap acts as a map of an entity id (or attribute value) to a map of an entity’s attributes.

The supplied data tuples may have the value true or false for the added field, but tuples with a false value are ignored. Note that the incoming data tuples may include data for multiple different types of entities.

By default, if the attributes are not aggregated into a struct, the attribute map will contain an extra field :”datom/e” whose value is the entity ID.

The following options are supported:

:cardinality_many - the name or names of attribute keys that correspond to cardinality/many attributes. If you have such attributes in your data, this option is required. The value for this option may be a single value, a list, or a set. The name should be the name of the attribute on the incoming data, irrespective of any aggregation. If a data tuple contains a scalar value for a cardinality many attribute, then that value is added to the set of values for t hat attribute. If a data tuple contains a collection as a value for a cardinality many attribute, the values are added to the set of values for that attribute. Note that if a cardinality many attribute is not present in the data tuples for an entity, it will not be present in the resulting attribute map; trying to get its value won’t give you an empty set; it will give you null.

:aggregate_into - this should be a pair, the first element of which is a module (i.e., the struct you wish to use to aggregate results) and the second of which is a map from keys in the raw data to fields of the struct. It is not necessary to map keys that have the same name as fields in the struct, but only keys that need to be translated. The aggregator is stored with the entity map; it is assumed that all the DataTuples or records that you will be adding or removing later have the same relevant attributes and so will be aggregated the same way.

:index_by - if you wish to use something other than the entity ID as the key for the EntityMap, specify the attribute name here. If you are aggregating the map into a struct, this should be the name of the field in the struct rather than the name of the attribute key in the data used to construct the EntityMap.

Example

d1 = %Datom{e: 0, a: :attr1, v: :value, tx: 0, added: true}
d2 = %Datom{e: 0, a: :attr2, v: :value2, tx: 0, added: true}
d3 = %Datom{e: 1, a: :attr2, v: :value3, tx: 0, added: true}
d4 = %Datom{e: 1, a: :attr3, v: :value2, tx: 0, added: false}

entity_map = EntityMap.new([d1, d2, d3, d4])

EntityMap.get_attr(entity_map, 1, :attr2)
  => :value3
put(entity_map, record, primary_key)

Specs

put(EntityMap.t, map, term) :: EntityMap.t

Returns an EntityMap with the given entity added. The supplied entity must be in the form of a map of attributes to values, including one attribute that specifies the entity ID (i.e., a value that would appear in the e: field of a Datom or DataTuple).

The key of the field containing the entity ID should be passed to the function as the third argument. If an entity already exists for a given ID, it will be replaced with the new one.

Example

d1 = %Datom{e: 0, a: :name, v: "Bill Smith", tx: 0, added: true}
d2 = %Datom{e: 0, a: :age, v: 32, tx: 0, added: true}
d3 = %Datom{e: 0, a: :identifier, v: :bill_smith, tx: 0, added: true}

result_struct = {TestPerson, %{identifier: :id, name: :names}}
entity_map = EntityMap.new([d1, d2, d3], 
                cardinality_many: :name, index_by: :id, aggregate_into: result_struct)

new_record = %{eid: 1, identifier: :jim_stewart, name: "Jim Stewart", age: 23}

result = EntityMap.put(entity_map, new_record, :eid)

EntityMap.get(result, :jim_stewart)
  => %TestPerson{age: 23, id: :jim_stewart, names: #MapSet<["Jim Stewart"]>}
put_attr(entity_map, index_key, attr_key, val, options \\ [])

Specs

put_attr(EntityMap.t, term, term, term, [{atom, boolean}]) ::
  {:ok, EntityMap.t} |
  {:error, String.t}

When successful, returns an :ok tuple containing an EntityMap with an updated value for a given entity’s attribute, where the entity is specified by its index key. If either the index or attribute key does not exist, an :error tuple is returned.

If the EntityMap is not indexed, the the index key should be the entity ID. If the EntityMap is aggregated, the attribute key should the name of the field in the aggregated struct; the attribute names in the raw data are not used.

Example

d1 = %Datom{e: 0, a: :name, v: "Bill Smith", tx: 0, added: true}
d2 = %Datom{e: 0, a: :age, v: 32, tx: 0, added: true}
d3 = %Datom{e: 0, a: :identifier, v: :bill_smith, tx: 0, added: true}
d4 = %Datom{e: 1, a: :name, v: "Karina Jones", tx: 0, added: true}
d5 = %Datom{e: 1, a: :age, v: 64, tx: 0, added: true}
d6 = %Datom{e: 1, a: :identifier, v: :karina_jones, tx: 0, added: true}

result_struct = {TestPerson, %{identifier: :id, name: :names}}
entity_map = EntityMap.new([d1, d2, d3, d4, d5, d6], 
                cardinality_many: :name, index_by: :id, aggregate_into: result_struct)

{:ok, updated} = EntityMap.put_attr(entity_map, :karina_jones, :age, 34)

EntityMap.get_attr(updated, :karina_jones, :age)
  => 34
rename_keys(map, key_rename_map)

Specs

rename_keys(map, map) :: map

A utility function that accepts a map and a translation table, and returns a new map whose keys have been renamed according to the translation table.

Example

input_map = %{eid: 1, identifier: :jim_stewart, name: "Jim Stewart", age: 23}
translation_table = %{eid: :id, identifier: :unique_name}

EntityMap.rename_keys(input_map, translation_table)
  => %{age: 23, id: 1, name: "Jim Stewart", unique_name: :jim_stewart}
set_defaults(options)

Specs

set_defaults([entity_map_option]) :: [entity_map_option]
update(entity_map, data_tuples_to_update)

Specs

update(EntityMap.t, [DatomicGenServer.EntityMap.DataTuple.t]) :: EntityMap.t

Returns an EntityMap created from an existing EntityMap and a list of data tuples (both retractions and additions).

Retractions are applied first. Nil or empty collection values in the data tuples to be retracted result in the entire pre-existing value being retracted. For cardinality many attributes, scalar values to be retracted are removed from the set of pre-existing values; collection values to be retracted are subtracted from the pre-existing set.

Similarly, the addition of nil or an empty collection removes the attribute (for a scalar value) or sets it to the empty collection (for a cardinality many value). Otherwise, for cardinality many attributes, scalar values to be added are added to the set of pre-existing values; collection values to be added are unioned with the pre-existing set.

After retractions and additions are applied, the map is re-aggregated and re-indexed according to its aggregator and index (if any).

Example

empty_map = EntityMap.new()

d1 = %Datom{e: 0, a: :name, v: "Bill Smith", tx: 0, added: true}
d2 = %Datom{e: 0, a: :age, v: 32, tx: 0, added: true}
d3 = %Datom{e: 1, a: :name, v: "Karina Jones", tx: 0, added: true}
d4 = %Datom{e: 1, a: :age, v: 64, tx: 0, added: true}

datoms_to_update = [d1, d2, d3, d4]

result = EntityMap.update(empty_map, datoms_to_update)

EntityMap.get_attr(result, 0, :age)
  => 32
update_from_records(entity_map, record_maps, primary_key)

Specs

update_from_records(EntityMap.t, Enum.t, term) :: EntityMap.t

Returns an EntityMap created from an existing EntityMap and a list of records. A record is a map of attribute names to values. One of those attribute keys must point to the entity primary key — i.e., the same value that would appear in the e value of a corresponding datom or DataTuple. This attribute should be passed to the function as the third argument.

The use of nil or an empty collection as a value for an attribute removes the attribute (for a scalar value) or sets it to the empty collection (for a cardinality many attribute). Otherwise, any attribute value supplied in a record overwrites the pre-existing value for that attribute; there is no way to add entries to or subtract them from a cardinality many attribute value using records.

After the map is updated with the new records, it is re-aggregated and re-indexed according to the map’s aggregator and index (if any).

Example

d1 = %{id: 1, attr1: [:value1, :value1a]}
d2 = %{id: 2, attr2: [:value2]}

initial_map = EntityMap.from_records([d1, d2], :id, cardinality_many: [:attr1])

d5 = %{id: 1, attr1: []}
d6 = %{id: 2, attr2: :value2a}

result = EntityMap.update_from_records(initial_map, [d5, d6], :id)

EntityMap.get_attr(result, 2, :attr2)
  => :value2a
update_from_rows(entity_map, rows_to_add, header, primary_key)

Specs

update_from_rows(EntityMap.t, [list] | MapSet.t, list, term) :: EntityMap.t

Returns an EntityMap created from an existing EntityMap and a list of rows. A row is a simple list of attribute values. The header (third parameter) supplies a list of the attribute names for these values.

One of those attribute keys must point to the entity’s primary key — i.e., the same value that would appearin the e value of a corresponding datom or DataTuple. This key should be supplied as the fourth parameter to the function.

The use of nil or an empty collection as a value for an attribute removes the attribute (for a scalar value) or sets it to the empty collection (for a cardinality many attribute). Otherwise, any attribute value supplied in a record overwrites the pre-existing value for that attribute; there is no way to add entries to or subtract them from a cardinality many attribute value using records.

After the map is updated with the new records, it is re-aggregated and re-indexed according to the map’s aggregator and index (if any).

Example

header = [:eid, :unique_name, :name, :age]

d1 = [1, :bill_smith, "Bill Smith", 32]
d2 = [2, :karina_jones, ["Karina Jones", "Karen Jones"], 64]

result_struct = {TestPerson, %{unique_name: :id, name: :names}}

initial_map = EntityMap.from_rows([d1, d2], header, :eid, 
                cardinality_many: [:name], index_by: :id, aggregate_into: result_struct)

d3 = [1, :bill_smith, "Bill Smith", 33]
d4 = [2, :karina_jones, MapSet.new(["Karen Jones"]), 64]

result = EntityMap.update_from_rows(initial_map, [d3, d4], header, :eid)

EntityMap.get_attr(result, :karina_jones, :names)
  => #MapSet<["Karen Jones"]>
update_from_transaction(entity_map, transaction)

Specs

update_from_transaction(EntityMap.t, DatomicTransaction.t) :: EntityMap.t

Returns an EntityMap created from an existing EntityMap and the datoms in a DatomicTransaction. These are passed to the update function (see the documentation on that function).

Example

header = [:eid, :unique_name, :name, :age]

d1 = [0, :bill_smith, "Bill Smith", 32]
d2 = [1, :karina_jones, ["Karina Jones", "Karen Jones"], 64]

result_struct = {TestPerson, %{unique_name: :id, name: :names}}

initial_map = EntityMap.from_rows([d1, d2], header, :eid, 
                cardinality_many: [:name], index_by: :id, aggregate_into: result_struct)

d3 = %Datom{e: 0, a: :name, v: "Bill Smith", tx: 0, added: false}
d4 = %Datom{e: 1, a: :name, v: "Karen Jones", tx: 0, added: false}

d5 = %Datom{e: 1, a: :name, v: "K. Jones", tx: 0, added: true}
d6 = %Datom{e: 2, a: :name, v: "Hartley Stewart", tx: 0, added: true}
d7 = %Datom{e: 2, a: :age, v: 44, tx: 0, added: true}
d8 = %Datom{e: 2, a: :unique_name, v: :hartley_stewart, tx: 0, added: true}

transaction = %DatomicTransaction{
  basis_t_before: 1000, 
  basis_t_after: 1001, 
  retracted_datoms: [d3, d4], 
  added_datoms: [d5, d6, d7, d8], 
  tempids: %{-1432323 => 64}
}

result = EntityMap.update_from_transaction(initial_map, transaction)

EntityMap.get(result, :karina_jones)
  => %TestPerson{age: 64, id: :karina_jones, names: #MapSet<["K. Jones", "Karina Jones"]>}
values(entity_map)

Specs

values(EntityMap.t) :: [term]

Returns all entities from the EntityMap, in a list.

Example

d1 = %Datom{e: 0, a: :name, v: "Bill Smith", tx: 0, added: true}
d2 = %Datom{e: 0, a: :age, v: 32, tx: 0, added: true}
d3 = %Datom{e: 0, a: :identifier, v: :bill_smith, tx: 0, added: true}
d4 = %Datom{e: 1, a: :name, v: "Karina Jones", tx: 0, added: true}
d5 = %Datom{e: 1, a: :age, v: 64, tx: 0, added: true}
d6 = %Datom{e: 1, a: :identifier, v: :karina_jones, tx: 0, added: true}

result_struct = {TestPerson, %{identifier: :id, name: :names}}
entity_map = EntityMap.new([d1, d2, d3, d4, d5, d6], 
                cardinality_many: :name, aggregate_into: result_struct)

EntityMap.values(entity_map)
  => [%TestPerson{age: 32, id: :bill_smith, names: #MapSet<["Bill Smith"]>},
      %TestPerson{age: 64, id: :karina_jones, names: #MapSet<["Karina Jones"]>}]