Relation Algebra Functions
WooriDB has some support to relation algebra functions as well as auxiliary functions to relation algebra. They are:
Functions GROUP BY
, ORDER BY
, DEDUP
LIMIT
, OFFSET
, COUNT
are only supported by the following select queries:
SELECT */#{...} FROM tree_key_name
SELECT */#{...} FROM tree_key_name WHERE {...}
SELECT */#{...} FROM tree_key_name IDS IN #{...}
Functions UNION
,INTERSECT
,DIFFERENCE
are only supported by the following select queries:
SELECT */#{...} FROM tree_key_name ID some-uuid
SELECT */#{...} FROM tree_key_name ID some-uuid WHEN AT some-date
GROUP BY
This groups the responses of the select query in the following type HashMap<String, BTreeMap<Uuid, HashMap<String, Types>>>
(for group by
associated with order by
the type is HashMap<String, Vec<(Uuid, HashMap<String, Types>)>>
). So the query SELECT * FROM key GROUP BY c
for the following 6 entities:
#![allow(unused)] fn main() { {a: 123, b: 12.3,} {a: 235, b: 12.3, c: 'c',} {a: 235, b: 12.3, c: 'd',} {a: 25, b: 12.3, c: 'c',} {a: 475, b: 12.3, c: 'd',} {a: 295, b: 12.3, c: 'r',} }
Will produce the response:
#![allow(unused)] fn main() { { "Char(\'r\')": {<Uuid6>: {a: 295, b: 12.3, c: 'r',},} , "Char(\'c\')": {<Uuid2>: {a: 235, b: 12.3, c: 'c',}, <Uuid4>: {a: 25, b: 12.3, c: 'c',},}, "Char(\'d\')": {<Uuid3>: {a: 235, b: 12.3, c: 'd',}, <Uuid5>: {a: 475, b: 12.3, c: 'd',},}, "Nil": {<Uuid1>: {a: 123, b: 12.3,},}, } }
- Note that the Hash of the type is a
String
containing awql::Types
.
ORDER BY
This functions orders the response of the query by the value of a key. The key-value can be ordered by :asc
or :desc
. So the query SELECT * FROM key ORDER BY a :asc
will return a Vec<(Uuid, HashMap<String, Types>)>
for the following 6 entities:
#![allow(unused)] fn main() { {a: 123, b: 12.3,} {a: 235, b: 12.3, c: 'c',} {a: 235, b: 12.3, c: 'd',} {a: 25, b: 12.3, c: 'c',} {a: 475, b: 12.3, c: 'd',} {a: 295, b: 12.3, c: 'r',} }
Will produce the response:
#![allow(unused)] fn main() { [ (<Uuid4>, {a: 25, b: 12.3, c: 'c',}), (<Uuid1>, {a: 123, b: 12.3,}), (<Uuid2>, {a: 235, b: 12.3, c: 'c',}), (<Uuid3>, {a: 235, b: 12.3, c: 'd',}), (<Uuid6>, {a: 295, b: 12.3, c: 'r',}), (<Uuid5>, {a: 475, b: 12.3, c: 'd',}), ] }
-
Order By with multiple arguments. The problem here is how to have multiple
.and_then(...)
alter thepartial_cmp
.
DEDUP
This functios is capable of removing duplicates key-values in responses. By using SELECT * FROM key DEDUP a
for the following 6 entities:
#![allow(unused)] fn main() { {a: 123, b: 12.3,} {a: 235, b: 12.3, c: 'c',} {a: 235, b: 12.3, c: 'd',} {a: 25, b: 12.3, c: 'c',} {a: 475, b: 12.3, c: 'd',} {a: 295, b: 12.3, c: 'r',} }
We would have as a result something like:
#![allow(unused)] fn main() { { <Uuid1>: {a: 123, b: 12.3,}, <Uuid2>: {a: 235, b: 12.3, c: 'c',}, <Uuid3>: {a: 25, b: 12.3, c: 'c',}, <Uuid4>: {a: 475, b: 12.3, c: 'd',}, <Uuid5>: {a: 295, b: 12.3, c: 'r',}, } }
Also it is possible to eliminate Nil
and Types::Nil
values for a DEDUP
key. This is done by calling the function NIL()
(It needs to be UPPERCASE) with the key used for the DEDUP
. So for the previous data the response for the query SELECT * FROM key DEDUP NIL(c)
would be:
#![allow(unused)] fn main() { { <Uuid2>: {a: 235, b: 12.3, c: 'c',}, <Uuid4>: {a: 475, b: 12.3, c: 'd',}, <Uuid5>: {a: 295, b: 12.3, c: 'r',}, } }
LIMIT
and OFFSET
The functions LIMIT
and OFFSET
expect a positive integer as argument, this means that if you define LIMIT 10
and OFFSET 5
you will skip the first 5 elements from the tree and take only the next 10 elements. LIMIT
and OFFSET
are also appended to the end of the select query such that SELECT * FROM key LIMIT 100 OFFSET 300
.
COUNT
This function is appended to the end of a select query and it will return the count for entities found by that select. So a query like SELECT * FROM key WHERE {...} COUNT
will return the responses for select where as well as the count of entities found in that select. The aswer will be in the following structure:
#![allow(unused)] fn main() { ( response: { "map containing the response for the query" }, count: usize, ) }
UNION
This unites two entities into one entity. There are two strategies for this relation the first one is UNION KEY
which will unify 2 entities adding to the first one the missing values from the second, then there is UNION KEY-VALUE
that will unite the keys and values from the second and if the value is the different for each key a duplicated
sign will be added. The following examples will help you understand considering the following entities:
#![allow(unused)] fn main() { { "ent1": {<UUID1>: {a: 123, b: 234, c: true,}} "ent2": {<UUID2>: {a: 123, b: 432, d: false,}} } }
KEY
UNION KEY Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2
. Note the |
as query separator.
The entity to be returned will be:
#![allow(unused)] fn main() { {"a": 123, "b": 234, "c": true, "d": false} }
KEY-VALUE
UNION KEY-VALUE Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2
. Note the |
as query separator.
The entity to be returned will be:
#![allow(unused)] fn main() { {"a": 123, "b": 234, "b:duplicated": 432, "c": true, "d": false} }
INTERSECT
This intersects two entities into one entity. There are two strategies for this relation the first one is INTERSECT KEY
which will return only the key value pairs from the first entity that have a corresponding key in the second entity, then there is INTERSECT KEY-VALUE
which will return only the key value pairs from the first entity that have a corresponding key value pair in the second entity. The following examples will help you understand considering the following entities:
#![allow(unused)] fn main() { { "ent1": {<UUID1>: {a: 123, b: 234, c: true,}} "ent2": {<UUID2>: {a: 123, b: 432, d: false,}} } }
KEY
INTERSECT KEY Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2
. Note the |
as query separator.
The entity to be returned will be:
#![allow(unused)] fn main() { {"a": 123, "b": 234} }
KEY-VALUE
INTERSECT KEY-VALUE Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2
. Note the |
as query separator.
The entity to be returned will be:
#![allow(unused)] fn main() { {"a": 123} }
DIFFERENCE
This intersects two entities into one entity. There are two strategies for this relation the first one is DIFFERENCE KEY
which will return only the key value pairs from the first entity that do not have a corresponding key in the second entity, then there is DIFFERENCE KEY-VALUE
which will return only the key value pairs from the first entity that do not have a corresponding key value pair in the second entity. The following examples will help you understand considering the following entities:
#![allow(unused)] fn main() { { "ent1": {<UUID1>: {a: 123, b: 234, c: true,}} "ent2": {<UUID2>: {a: 123, b: 432, d: false,}} } }
KEY
DIFFERENCE KEY Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2
. Note the |
as query separator.
The entity to be returned will be:
#![allow(unused)] fn main() { {"c": true,} }
KEY-VALUE
DIFFERENCE KEY-VALUE Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2
. Note the |
as query separator.
The entity to be returned will be:
#![allow(unused)] fn main() { {"c": true, "b": 234} }
JOIN
Join operation is similar to UNION
. However, it does this by comparing keys equallity in two different entities, so if we select all elements in entity_a
and all elements in entity_b
and we join them in key a
for entity_a
and key b
for entity_b
whenever entity_a:a == entity_b:b
a new entity will be created and appended to the resulting vector. Also all duplciated keys from entity_b
will be appended by :entity_b
, so a duplicated key dup_key
will be dup_key:entity_b
.
For the query JOIN (entity_AA:c, entity_BB:o) Select * FROM entity_AA order by c :asc | Select #{{g, f, o, b,}} FROM entity_BB
we are checking equallity on entity_AA:c == entity_BB:o
and the two queries two to be joined are Select * FROM entity_AA order by c :asc
and Select #{{g, f, o, b,}} FROM entity_BB
joined by a |
.
#![allow(unused)] fn main() { { "entity_AA": { <UUID1>: {a: 123, b: 12.3,}, <UUID3>: {a: 235, b: 17.3, c: 'c',}, <UUID5>: {a: 476, b: 312.3, c: 'd',}, <UUID7>: {a: 857, c: 'd',},} "entity_BB": { <UUID2>: {a: 66, b: 66.3, c: 'r',}, <UUID4>: {g: 25, f: 12.3, a: 'c',}, <UUID6>: {g: 475, b: 12.3, f: 'h', o: 'd',}, <UUID8>: {g: 756, b: 142.3, f: 'h', o: 'c',}, <UUI10>: {g: 76, b: 12.3, f: 't', o: 'd',}, <UUID12>: {t: 295, b: 12.3, o: 'r',}, <UUID14>: {t: 295, f: 12.3, o: Nil,}, } } }
The response for this join will be:
Notes
tx_time
is thetx_time
ofentity_AA
.- entities that don't have the field to be matched will be matched with other entities that don't have the field or it is
nil
.
#![allow(unused)] fn main() { [ { "tx_time": DateTime( 2021-04-01T18:04:30.029549132Z, ), "a": Integer( 123, ), "b": Float( 12.3, ), "g": Integer( 25, ), "f": Float( 12.3, ), }, { "tx_time": DateTime( 2021-04-01T18:04:30.029549132Z, ), "a": Integer( 123, ), "b": Float( 12.3, ), "b:entity_BB": Float( 66.3, ), }, { "tx_time": DateTime( 2021-04-01T18:04:30.029549132Z, ), "a": Integer( 123, ), "b": Float( 12.3, ), "f": Float( 12.3, ), }, { "g": Integer( 756, ), "f": Char( 'h', ), "b": Float( 17.3, ), "tx_time": DateTime( 2021-04-01T18:04:30.030481424Z, ), "a": Integer( 235, ), "b:entity_BB": Float( 142.3, ), "c": Char( 'c', ), }, { "b:entity_BB": Float( 12.3, ), "tx_time": DateTime( 2021-04-01T18:04:30.031485453Z, ), "f": Char( 't', ), "g": Integer( 76, ), "a": Integer( 476, ), "b": Float( 312.3, ), "c": Char( 'd', ), }, { "f": Char( 'h', ), "tx_time": DateTime( 2021-04-01T18:04:30.031485453Z, ), "b:entity_BB": Float( 12.3, ), "g": Integer( 475, ), "a": Integer( 476, ), "b": Float( 312.3, ), "c": Char( 'd', ), }, { "b": Float( 12.3, ), "c": Char( 'd', ), "tx_time": DateTime( 2021-04-01T18:04:30.032730665Z, ), "g": Integer( 76, ), "a": Integer( 857, ), "f": Char( 't', ), }, { "b": Float( 12.3, ), "c": Char( 'd', ), "tx_time": DateTime( 2021-04-01T18:04:30.032730665Z, ), "g": Integer( 475, ), "a": Integer( 857, ), "f": Char( 'h', ), }, ] }