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 DataTuple
s. 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
Functions
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}
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
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
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"]>}
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"]>}
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
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}
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
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
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"]>}
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]
Specs
new([DatomicGenServer.EntityMap.DataTuple.t], [entity_map_option]) :: EntityMap.t
Creates a new EntityMap
from a list of DataTuple
s. 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
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"]>}
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
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}
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
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
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"]>
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"]>}
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"]>}]