agentm / project-m36

Project: M36 Relational Algebra Engine
The Unlicense
876 stars 47 forks source link

Recursive relationships #348

Open jmatsushita opened 1 year ago

jmatsushita commented 1 year ago

Hi there πŸ‘‹

I'm trying to work with recursive relationships.

TutorialD (master/main): rec := relation{tuple{src 0, tgt 1}, tuple{src 1, tgt 2}, tuple{src 2, tgt 3}}
TutorialD (master/main): :showexpr rec
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚src::Integerβ”‚tgt::Integerβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚0           β”‚1           β”‚
β”‚1           β”‚2           β”‚
β”‚2           β”‚3           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

It seems to work ok for a depth 1 traversal:

TutorialD (master/main): :showexpr ((rec where src=0) join (rec rename {tgt as tgt2, src as tgt}))
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚src::Integerβ”‚tgt::Integerβ”‚tgt2::Integerβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚0           β”‚1           β”‚2            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Also if I add an edge:

TutorialD (master/main): insert rec relation{tuple{src 1, tgt 4}}
TutorialD (master/main): :showexpr rec
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚src::Integerβ”‚tgt::Integerβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚1           β”‚4           β”‚
β”‚2           β”‚3           β”‚
β”‚1           β”‚2           β”‚
β”‚0           β”‚1           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
TutorialD (master/main): :showexpr ((rec where src=0) join (rec rename {tgt as tgt2, src as tgt}))
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚src::Integerβ”‚tgt::Integerβ”‚tgt2::Integerβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚0           β”‚1           β”‚2            β”‚
β”‚0           β”‚1           β”‚4            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

I can work out a way to do a depth 2 traversal:

TutorialD (master/main): :showexpr ((rec where src=0) join (rec rename {tgt as tgt2, src as tgt}) join (rec rename {tgt as tgt3, src as tgt2}))
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚src::Integerβ”‚tgt::Integerβ”‚tgt2::Integerβ”‚tgt3::Integerβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚0           β”‚1           β”‚2            β”‚3            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Can this be generalised to a depth n traversal? Also would it be possible to group the result by depth?

Thanks for your time!

Jun

jmatsushita commented 1 year ago

Also unfortunately:

TutorialD (master/main): foreign key src_tgt rec{src} in rec{tgt}
ERR: RelationTypeMismatchError attributesFromList [(Attribute "src" IntegerAtomType)] attributesFromList [(Attribute "tgt" IntegerAtomType)]

UPDATE: I think this is non-sensical though πŸ˜…

jmatsushita commented 1 year ago

Ok what I was really trying to do was:

TutorialD (master/main): nod := relation{tuple{nid 0, lbl "a"}}
TutorialD (master/main): insert nod relation{tuple{nid 1, lbl "b"}}
TutorialD (master/main): :showexpr nod
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚lbl::Textβ”‚nid::Integerβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚"b"      β”‚1           β”‚
β”‚"a"      β”‚0           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
TutorialD (master/main): foreign key src_nod (rec rename {src as nid}){nid} in nod{nid}
ERR: InclusionDependencyCheckError "src_nod" Nothing
TutorialD (master/main): insert nod relation{tuple{nid 2, lbl "c"}}
TutorialD (master/main): insert nod relation{tuple{nid 3, lbl "d"}}
TutorialD (master/main): insert nod relation{tuple{nid 4, lbl "e"}}
TutorialD (master/main): foreign key src_nod (rec rename {src as nid}){nid} in nod{nid}
TutorialD (master/main): foreign key tgt_nod (rec rename {tgt as nid}){nid} in nod{nid}

Works nicely

jmatsushita commented 1 year ago

Oh wow, actually this does work if I make the root loop on itself (tuple {src 0, tgt 0}) and map src and tgt attributes to another name x:

TutorialD (master/main): rec := relation{tuple{src 0, tgt 0},tuple{src 0, tgt 1}, tuple{src 1, tgt 2}, tuple{src 2, tgt 3}}
TutorialD (master/main): foreign key src_tgt (rec rename {src as x}){x} in (rec rename {tgt as x}){x}
agentm commented 1 year ago

That's a clever workaround. I suppose it wouldn't hurt to make a utility wrapper command to make this pattern more convenient. Do you have some syntax in mind?

By the way, the foreign key syntax is itself a convenience wrapper around inclusion dependencies which have been proven to be able to represent any database constraint.

agentm commented 1 year ago

I've also been thinking about how to represent n-ary, nested relationships within a relation outside the scope of adjacency lists since we could leverage nested relations. However, the top-level relation type does not seem to be able to represent arbitrarily-nested relation types, so I don't see off-hand how to represent arbitrary levels of nesting.

jmatsushita commented 1 year ago

Thanks for the feedback πŸ™ I'll try to think about a syntax for traversing recursive relationships. Datalog might be an inspiration ? I'm very interested in modeling n-ary relations but I don't quite visualise how to do it with a nested relation even for the arity 2 case. Could you give an example?

jmatsushita commented 1 year ago

Sort of broadening the scope quite a bit here, but I was wondering what are principled approaches to model graphs in a relation database. This seems like a very interesting paper on implementing graph algorithms in RDBMSes using new operations and a while syntax.

https://dsl.cds.iisc.ac.in/~course/TIDS/papers/allinone.pdf

To support graph processing, in this work, we propose 4 new relational algebra operations, MM- join, MV-join, anti-join, and union-by-update.

agentm commented 1 year ago

Thanks for linking to that paper- it proposes new SQL syntax to support said traversals. However, it is still limited by the capabilities of SQL (which Project:M36 is not). For example, Project:M36 supports algebraic data types and relation-valued attributes, both of which can be leveraged to represent graph more effectively.

For example, relation-valued attributes would be a great way to query a database to service an ORM. Here's a sample dashboard using the C.J. Date example. This would typically done by an outer join in SQL, but the Project:M36 representation is quite natural:

:showexpr (s join sp){p#,qty,s#,sname} group ({p#,qty} as parts)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚parts::relation {p#::Text,qty::Integer}β”‚s#::Textβ”‚sname::Textβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”‚"S4"    β”‚"Clark"    β”‚
β”‚β”‚p#::Textβ”‚qty::Integerβ”‚                β”‚        β”‚           β”‚
β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                β”‚        β”‚           β”‚
β”‚β”‚"P4"    β”‚300         β”‚                β”‚        β”‚           β”‚
β”‚β”‚"P5"    β”‚400         β”‚                β”‚        β”‚           β”‚
β”‚β”‚"P2"    β”‚200         β”‚                β”‚        β”‚           β”‚
β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚        β”‚           β”‚
β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”‚"S1"    β”‚"Smith"    β”‚
β”‚β”‚p#::Textβ”‚qty::Integerβ”‚                β”‚        β”‚           β”‚
β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                β”‚        β”‚           β”‚
β”‚β”‚"P4"    β”‚200         β”‚                β”‚        β”‚           β”‚
β”‚β”‚"P5"    β”‚100         β”‚                β”‚        β”‚           β”‚
β”‚β”‚"P6"    β”‚100         β”‚                β”‚        β”‚           β”‚
β”‚β”‚"P3"    β”‚400         β”‚                β”‚        β”‚           β”‚
β”‚β”‚"P1"    β”‚300         β”‚                β”‚        β”‚           β”‚
β”‚β”‚"P2"    β”‚200         β”‚                β”‚        β”‚           β”‚
β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚        β”‚           β”‚
β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”‚"S3"    β”‚"Blake"    β”‚
β”‚β”‚p#::Textβ”‚qty::Integerβ”‚                β”‚        β”‚           β”‚
β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                β”‚        β”‚           β”‚
β”‚β”‚"P2"    β”‚200         β”‚                β”‚        β”‚           β”‚
β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚        β”‚           β”‚
β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”‚"S2"    β”‚"Jones"    β”‚
β”‚β”‚p#::Textβ”‚qty::Integerβ”‚                β”‚        β”‚           β”‚
β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                β”‚        β”‚           β”‚
β”‚β”‚"P2"    β”‚400         β”‚                β”‚        β”‚           β”‚
β”‚β”‚"P1"    β”‚300         β”‚                β”‚        β”‚           β”‚
β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚        β”‚           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

We could consider some syntax addition to TutorialD which would generate graph traversals using RVAs, but I'm not yet sure what that would look like.

YuMingLiao commented 6 months ago

I encounter this issue too when I want to record tree-like comments.

I found an article talking about tree structure in sql (in Chinese). Tree structure in SQL

Basically, it says there are four ways to do that:

Adjacency List: easiest to implement and understand Path Enumeration: convenient when paths are needed Nested Sets: good for query frequently but modify occasionally. Closure Table: flexible, needs extra space.

agentm commented 6 months ago

There's actually a more natural way to represent graphs in the relational algebra which I have not seen before. It's not quite possible in Project:M36 because we don't support recursive data types, but it should be possible to support in the future.

Here's an example that works today that hints at what could be possible:

TutorialD (master/main): :showexpr relation{tuple{val 4, children relation{tuple{val 6,children relation{tuple{}}}}},                   tuple{val 10, children relation{tuple{val 1, children relation{tuple{}}},                                                   tuple{val 2, children relation{tuple{}}}}}}
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚children::relation {children::relation {},val::Integer}β”‚val::Integerβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚4           β”‚
β”‚β”‚children::relation {}β”‚val::Integerβ”‚                   β”‚            β”‚
β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                   β”‚            β”‚
β”‚β”‚β”Œβ”                   β”‚6           β”‚                   β”‚            β”‚
β”‚β”‚β”‚β”‚                   β”‚            β”‚                   β”‚            β”‚
β”‚β”‚β”œβ”€                   β”‚            β”‚                   β”‚            β”‚
β”‚β”‚β””β”˜                   β”‚            β”‚                   β”‚            β”‚
β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚            β”‚
β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚10          β”‚
β”‚β”‚children::relation {}β”‚val::Integerβ”‚                   β”‚            β”‚
β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                   β”‚            β”‚
β”‚β”‚β”Œβ”                   β”‚2           β”‚                   β”‚            β”‚
β”‚β”‚β”‚β”‚                   β”‚            β”‚                   β”‚            β”‚
β”‚β”‚β”œβ”€                   β”‚            β”‚                   β”‚            β”‚
β”‚β”‚β””β”˜                   β”‚            β”‚                   β”‚            β”‚
β”‚β”‚β”Œβ”                   β”‚1           β”‚                   β”‚            β”‚
β”‚β”‚β”‚β”‚                   β”‚            β”‚                   β”‚            β”‚
β”‚β”‚β”œβ”€                   β”‚            β”‚                   β”‚            β”‚
β”‚β”‚β””β”˜                   β”‚            β”‚                   β”‚            β”‚
β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

If we could define a recursive type for the top-level relation, then we could conceivably enable unlimited levels of nested structures using nested relations. This would allow us, for example, to enable fine-grained constraints on the graph use normal database constraints.

This is certainly more natural than adjanceny lists and less error-prone than number range-based graphs structures.