Pandora takes a json-like tree made of primitive type leafs, lists and dicts and generates
- a collection of tables indexed by
path- columns of primitive types with values taken from tree leaves
nodeHashfield corresponding to the merkle tree node hash- useful for spotting duplicates, at any level
- not to be used as
id
- reproducibile
parentId,idandindexfields for each row
- No-dependency core package
- No schema management in the core: the target tables can be inconsistently typed and we let the user choose what to do with the inconsistencies by not resolving them
tablesfunction parameterised by arbitrary Leaf type, allowing for type-safe wrappers- The merge of the output of different records is left outside from the core (could be even left to the persistence layer)
- We want to make sure that records from nodes with identical underlying data but from trees that differ elsewhere are duplicated, because
- they might be part of different partitions in the target storage
- we want to know the unique parent id for every node in O(1), without performing joins
- we might want to delete some data (e.g. without affecting unrelated nodes)
- we might want to consider these records as different when doing aggregations (e.g. count)
- Collapsing tables at different paths is a responsibility left to post-processing
- the uses chooses the Path => String function, allowing recursive data models to be managed
We consider a single input json-like value at once and perform
- an initial pass where we compute the merkle-tree hashes bottom-up, and we annotate the tree with those
- another pass where we annotate each node and leaf of the tree with the
path - another top-down pass where we annotate every node with three values:
parentId- root node:
null - other nodes:
parent.id
- root node:
index- root node: 0
- based on the last component of the
pathfor all the other nodes
id = hash(parentId ++ index ++ nodeHash)nodeHashis needed to build theidfor the root node, but we consider it at all levels just for consistency
- a traversal where we extract only the annotations, and we accumulate the actual tables
- when the node is an array
- we add a table for the node path, if missing
- we build a single record with index
nulland no values
- when the node is an object
- we add a table for the node path, if missing
- we build a single record with index
null
- when the node is a leaf
- if its parent is an array
- we add a record with non-
nullindex to a new table
- we add a record with non-
- if its parent is an object
- we add our value to the parentId record in the field we compute from the path
- if its parent is an array
- when the node is an array
- Allow putting leaves in purely content-addressed
hash-indexed tables instead ofid-indexed tables
- reference those values using hashes in the
id-indexed leaf record - could be useful when you have large "flat" nodes, i.e. with lots of primitive type fields
- usually not what you want, as it requires expensive joins for simple aggregations
- requires garbage collection algorithm to handle
- Reduce the number of non-necessary tables
root_table_name = "root"
separator = "_"
single_value_field_name = "value"
array_elements_table_name_suffix = "elems"
{"x"-> [4,2,4], y -> "ciao" }
=>
table "root"
id | parent_id | node_hash | index | y
bdbd | <null> | zzbz | <null> | "ciao"
table "root_x"
id | parent_id | node_hash | index
ccbd | bdbd | iuim | <null>
table "root_x_elems"
id | parent_id | node_hash | index | value
cppc | ccbd | hbdh | 0 | 4
uhhy | ccbd | zsds | 1 | 2
zaza | ccbd | hbdh | 2 | 4
{"x" -> {"y" -> [], "z" -> {}}}
{"x" -> {"a" -> [1,2,3]}, "w" -> 10}
=>
table "root"
id | w
dqlw |
ghgh | 10
table "root_x"
id | parent_id | node_hash | index
bfbf | dqlw | zbzb | <null>
dsda | ghgh | lkmx | <null>
table "root_x_y"
id | parent_id | node_hash | index
gggz | bfbf | jdsd | <null>
table "root_z"
id | parent_id | node_hash | index
asds | dqlw | poji | <null>
table "root_x_a"
id | parent_id | node_hash | index
pcpc | dsda | bcbc | <null>
table "root_x_a_elems"
id | parent_id | node_hash | index | value
aasd | pcpc | dsda | 0 | 1
qweq | pcpc | dsda | 1 | 2
aasd | pcpc | dsda | 2 | 3
{"x" -> {"y" -> [], "z" -> {}}}
{"x" -> 10}
=>
table "root"
id | parent_id | node_hash | index | x
sdas | <null> | hdus | <null> | <null>
cycy | <null> | isup | <null> | 10
table "root_x"
id | parent_id | node_hash | index
dsda | sdas | ascd | <null>
table "root_x_y"
id | parent_id | node_hash | index
jdsa | dsda | jdjs | <null>
table "root_x_y_elems"
id | parent_id | node_hash | index | value
table "root_x_z"
id | parent_id | node_hash | index
dsae | sdas | kiuy | <null>
[{"a" -> 1, "b" -> "x"}, {"a" -> 3, "c" -> false}]
=>
table "root"
id | parent_id | node_hash | index
qwep | <null> | nckj | <null>
table "root_elems"
id | parent_id | node_hash | index | a | b | c
pisd | qwep | ncnx | 0 | 1 | "x" | <null>
oiuw | qwep | cxop | 1 | 3 | <null> | false
"x"
=>
table "root"
id | parent_id | node_hash | index
[[1,2,3], [4,5,6]]
=>
table "root"
id | parent_id | node_hash | index
xyxy | <null> | asda | <null>
table "root_elems"
id | parent_id | node_hash | index
baba | xyxy | dasd | 0
hdhd | xyxy | aspo | 1
table "root_elems_elems"
id | parent_id | node_hash | index | value
dsda | baba | oius | 0 | 1
sdff | baba | iuoi | 1 | 2
poip | baba | qweq | 2 | 3
jfij | hdhd | puji | 0 | 4
zxza | hdhd | oiuy | 1 | 5
huio | hdhd | asda | 2 | 6