The Forum for Discussion about The Third Manifesto and Related Matters

You need to log in to create posts and topics.

Update on "C# is a D" project

Quote from dandl on May 14, 2020, 10:34 am

I've added relational assignment and the conventional shortcuts: insert, update and delete.

It should be noted that these are all type safe and provide native type inference in the editor. In fact that's also true of select, while, union and friends. It's the operators that depend on the heading that can cause errors that need to be detected at runtime: project, rename, join and related.

// code
WriteLine("Insert P6 P7");
v1.Insert(
RelS.Create<RelS>(
new List<TupS> {
TupS.Create( "S6", "White", 25, "Paris" ),
TupS.Create( "S7", "Black", 15, "London" ),
})
);
WriteLine(v1.Format());
WriteLine("Move to Sydney");
v1.Update(t => t.City == "Paris", t => TupS.Create(t.SNo, t.SName, t.Status, "Sydney"));
WriteLine(v1.Format());
WriteLine("Delete Sydneysiders");
v1.Delete(t => t.City == "Sydney");
WriteLine(v1.Format());
// output
Insert P6 P7
SNo: S1, SName: Smith, Status: 20, City: London
SNo: S2, SName: Jones, Status: 10, City: Paris
SNo: S3, SName: Blake, Status: 30, City: Paris
SNo: S4, SName: Clark, Status: 20, City: London
SNo: S5, SName: Adams, Status: 30, City: Athens
SNo: S6, SName: White, Status: 25, City: Paris
SNo: S7, SName: Black, Status: 15, City: London
Move to Sydney
SNo: S1, SName: Smith, Status: 20, City: London
SNo: S2, SName: Jones, Status: 10, City: Sydney
SNo: S3, SName: Blake, Status: 30, City: Sydney
SNo: S4, SName: Clark, Status: 20, City: London
SNo: S5, SName: Adams, Status: 30, City: Athens
SNo: S6, SName: White, Status: 25, City: Sydney
SNo: S7, SName: Black, Status: 15, City: London
Delete Sydneysiders
SNo: S1, SName: Smith, Status: 20, City: London
SNo: S4, SName: Clark, Status: 20, City: London
SNo: S5, SName: Adams, Status: 30, City: Athens
SNo: S7, SName: Black, Status: 15, City: London
// code WriteLine("Insert P6 P7"); v1.Insert( RelS.Create<RelS>( new List<TupS> { TupS.Create( "S6", "White", 25, "Paris" ), TupS.Create( "S7", "Black", 15, "London" ), }) ); WriteLine(v1.Format()); WriteLine("Move to Sydney"); v1.Update(t => t.City == "Paris", t => TupS.Create(t.SNo, t.SName, t.Status, "Sydney")); WriteLine(v1.Format()); WriteLine("Delete Sydneysiders"); v1.Delete(t => t.City == "Sydney"); WriteLine(v1.Format()); // output Insert P6 P7 SNo: S1, SName: Smith, Status: 20, City: London SNo: S2, SName: Jones, Status: 10, City: Paris SNo: S3, SName: Blake, Status: 30, City: Paris SNo: S4, SName: Clark, Status: 20, City: London SNo: S5, SName: Adams, Status: 30, City: Athens SNo: S6, SName: White, Status: 25, City: Paris SNo: S7, SName: Black, Status: 15, City: London Move to Sydney SNo: S1, SName: Smith, Status: 20, City: London SNo: S2, SName: Jones, Status: 10, City: Sydney SNo: S3, SName: Blake, Status: 30, City: Sydney SNo: S4, SName: Clark, Status: 20, City: London SNo: S5, SName: Adams, Status: 30, City: Athens SNo: S6, SName: White, Status: 25, City: Sydney SNo: S7, SName: Black, Status: 15, City: London Delete Sydneysiders SNo: S1, SName: Smith, Status: 20, City: London SNo: S4, SName: Clark, Status: 20, City: London SNo: S5, SName: Adams, Status: 30, City: Athens SNo: S7, SName: Black, Status: 15, City: London
// code
      WriteLine("Insert P6 P7");
      v1.Insert(
        RelS.Create<RelS>(
          new List<TupS> {
            TupS.Create( "S6", "White", 25, "Paris" ),
            TupS.Create( "S7", "Black", 15, "London" ),
          })
        );
      WriteLine(v1.Format());

      WriteLine("Move to Sydney");
      v1.Update(t => t.City == "Paris", t => TupS.Create(t.SNo, t.SName, t.Status, "Sydney"));
      WriteLine(v1.Format());

      WriteLine("Delete Sydneysiders");
      v1.Delete(t => t.City == "Sydney");
      WriteLine(v1.Format());

// output
Insert P6 P7
SNo: S1, SName: Smith, Status: 20, City: London
SNo: S2, SName: Jones, Status: 10, City: Paris
SNo: S3, SName: Blake, Status: 30, City: Paris
SNo: S4, SName: Clark, Status: 20, City: London
SNo: S5, SName: Adams, Status: 30, City: Athens
SNo: S6, SName: White, Status: 25, City: Paris
SNo: S7, SName: Black, Status: 15, City: London
Move to Sydney
SNo: S1, SName: Smith, Status: 20, City: London
SNo: S2, SName: Jones, Status: 10, City: Sydney
SNo: S3, SName: Blake, Status: 30, City: Sydney
SNo: S4, SName: Clark, Status: 20, City: London
SNo: S5, SName: Adams, Status: 30, City: Athens
SNo: S6, SName: White, Status: 25, City: Sydney
SNo: S7, SName: Black, Status: 15, City: London
Delete Sydneysiders
SNo: S1, SName: Smith, Status: 20, City: London
SNo: S4, SName: Clark, Status: 20, City: London
SNo: S5, SName: Adams, Status: 30, City: Athens
SNo: S7, SName: Black, Status: 15, City: London

 

Reminds me of jOOQ (https://www.jooq.org), which I think has been mentioned here before. I presume there's a .NET equivalent?

I'm the forum administrator and lead developer of Rel. Email me at dave@armchair.mb.ca with the Subject 'TTM Forum'. Download Rel from https://reldb.org
Quote from Dave Voorhis on May 14, 2020, 10:08 am
Quote from Erwin on May 14, 2020, 8:55 am
Quote from Dave Voorhis on May 10, 2020, 9:07 am
Quote from dandl on May 10, 2020, 8:04 am
  • Generalised aggregation as per RM Pre 6 is straightforward.  Andl has this, but I'm not aware of any other D that has.

Rel does. See https://reldb.org/c/wp-content/uploads/2016/06/User-Defined-Aggregate-Operators-in-Tutorial-D-and-Rel.pdf

 

SIRA_PRISE has too.  See http://shark.armchair.mb.ca/~erwin/languageandgrammar_0105.html

<ROaggregate> := AGGREGATE(<RelationalExpression>,(<AggregationDef>+))

Doesn't SIRA_PRISE have generalised transitive closure, too?

Yup.

<ROgtclose> := GTCLOSE(<RelationalExpression>,(<AttributeNamePair>+,),(<GTCloseDef>*))

And in this case there is indeed justification for including it since there's not really a way to take the "use a recursive operator" path.

And there's also this little curious coincidence :

<ROTransform> := TRANSFORM(<RelationalExpression>,(<TransformDef>*,))
<TransformDef
> := <AttributeName>[(<Expression>)]

Quote from Erwin on May 14, 2020, 11:36 am
Quote from Dave Voorhis on May 14, 2020, 10:08 am
Quote from Erwin on May 14, 2020, 8:55 am
Quote from Dave Voorhis on May 10, 2020, 9:07 am
Quote from dandl on May 10, 2020, 8:04 am
  • Generalised aggregation as per RM Pre 6 is straightforward.  Andl has this, but I'm not aware of any other D that has.

Rel does. See https://reldb.org/c/wp-content/uploads/2016/06/User-Defined-Aggregate-Operators-in-Tutorial-D-and-Rel.pdf

 

SIRA_PRISE has too.  See http://shark.armchair.mb.ca/~erwin/languageandgrammar_0105.html

<ROaggregate> := AGGREGATE(<RelationalExpression>,(<AggregationDef>+))

Doesn't SIRA_PRISE have generalised transitive closure, too?

Yup.

<ROgtclose> := GTCLOSE(<RelationalExpression>,(<AttributeNamePair>+,),(<GTCloseDef>*))

And in this case there is indeed justification for including it since there's not really a way to take the "use a recursive operator" path.

Good to hear. That isn't really enough to work out what it can do: is it as general as while? (Which, by the way, is pretty much like SQL CTE RECURSIVE.)

And there's also this little curious coincidence :

<ROTransform> := TRANSFORM(<RelationalExpression>,(<TransformDef>*,))
<TransformDef
> := <AttributeName>[(<Expression>)]

Acknowledged. That one I did know about, and that influenced the choice of name. It may not be as 'pure' as the RA, but it sure can save on having to combine other operators to achieve a simple outcome.

Andl - A New Database Language - andl.org

Reminds me of jOOQ (https://www.jooq.org), which I think has been mentioned here before. I presume there's a .NET equivalent?

Although they target a similar general use case, they really are quite different. The jOOQ pipeline is about putting together the parts of an SQL statement without having to fuss too much about names and other details. It's very much a leaky abstraction. The mental model is assembling fragments of SQL code.

The .NET equivalent is LINQ for SQL. That compiles into an expression tree from which the SQL is generated. You get to use ordinary generics, functions and operators, not a DSL. The mental model is building an enumeration pipeline (like LINQ).

Both Andl and my original Andl.NET internally generate a deferred pipeline of tuples. In Andl it's well hidden, and the mental model is a pipeline of relation values; in Andl.NET it's quite visible, like LINQ. In both cases it's possible to build SQL, but you have little control over what it builds (compared to jOOQ).

This project isn't hiding anything: it really is building a pipeline of relation values. It should be possible to re-implement it with a pipeline of tuples behind the scenes, but it was easier this way. It will never look more than superficially like jOOQ.

But if devs will put up with a DSL and two kinds of code generation in their build path, surely the kind of type tweaks I propose are small beer?

 

Andl - A New Database Language - andl.org
Quote from Dave Voorhis on May 14, 2020, 11:10 am
Quote from dandl on May 14, 2020, 10:44 am
Quote from Dave Voorhis on May 14, 2020, 10:08 am
Quote from Erwin on May 14, 2020, 8:55 am
Quote from Dave Voorhis on May 10, 2020, 9:07 am
Quote from dandl on May 10, 2020, 8:04 am
  • Generalised aggregation as per RM Pre 6 is straightforward.  Andl has this, but I'm not aware of any other D that has.

Rel does. See https://reldb.org/c/wp-content/uploads/2016/06/User-Defined-Aggregate-Operators-in-Tutorial-D-and-Rel.pdf

 

SIRA_PRISE has too.  See http://shark.armchair.mb.ca/~erwin/languageandgrammar_0105.html

<ROaggregate> := AGGREGATE(<RelationalExpression>,(<AggregationDef>+))

Doesn't SIRA_PRISE have generalised transitive closure, too?

If not, I'm sure it could be added easily (and you should add it to Rel as well). A while operator just isn't hard. Happy to provide code if you need it (but I'm sure you don't).

I'm not sure there's much justification for a generalised transitive closure or while, because Tutorial DRel supports general-purpose recursive functions.

Obviously it's your choice, but often it's less work to just do it than to explain why not. How much 'justification' do you really need?

Andl - A New Database Language - andl.org
Quote from dandl on May 14, 2020, 2:21 pm
Quote from Dave Voorhis on May 14, 2020, 11:10 am
Quote from dandl on May 14, 2020, 10:44 am
Quote from Dave Voorhis on May 14, 2020, 10:08 am
Quote from Erwin on May 14, 2020, 8:55 am
Quote from Dave Voorhis on May 10, 2020, 9:07 am
Quote from dandl on May 10, 2020, 8:04 am
  • Generalised aggregation as per RM Pre 6 is straightforward.  Andl has this, but I'm not aware of any other D that has.

Rel does. See https://reldb.org/c/wp-content/uploads/2016/06/User-Defined-Aggregate-Operators-in-Tutorial-D-and-Rel.pdf

 

SIRA_PRISE has too.  See http://shark.armchair.mb.ca/~erwin/languageandgrammar_0105.html

<ROaggregate> := AGGREGATE(<RelationalExpression>,(<AggregationDef>+))

Doesn't SIRA_PRISE have generalised transitive closure, too?

If not, I'm sure it could be added easily (and you should add it to Rel as well). A while operator just isn't hard. Happy to provide code if you need it (but I'm sure you don't).

I'm not sure there's much justification for a generalised transitive closure or while, because Tutorial DRel supports general-purpose recursive functions.

Obviously it's your choice, but often it's less work to just do it than to explain why not. How much 'justification' do you really need?

I'd need to see that there are a lot of practical and pedagogical use cases that can't be practically or pedagogically easily addressed without generalised transitive closure or while.

I can see some use for generalised transitive closure, and at some point -- it's low priority -- I'll likely add it.

I can't see any use for while, as a user-defined recursive operator can achieve exactly the same thing but arguably more intuitively.

I'm the forum administrator and lead developer of Rel. Email me at dave@armchair.mb.ca with the Subject 'TTM Forum'. Download Rel from https://reldb.org
Quote from dandl on May 14, 2020, 2:18 pm

Reminds me of jOOQ (https://www.jooq.org), which I think has been mentioned here before. I presume there's a .NET equivalent?

Although they target a similar general use case, they really are quite different. The jOOQ pipeline is about putting together the parts of an SQL statement without having to fuss too much about names and other details. It's very much a leaky abstraction. The mental model is assembling fragments of SQL code.

The .NET equivalent is LINQ for SQL. That compiles into an expression tree from which the SQL is generated. You get to use ordinary generics, functions and operators, not a DSL. The mental model is building an enumeration pipeline (like LINQ).

Both Andl and my original Andl.NET internally generate a deferred pipeline of tuples. In Andl it's well hidden, and the mental model is a pipeline of relation values; in Andl.NET it's quite visible, like LINQ. In both cases it's possible to build SQL, but you have little control over what it builds (compared to jOOQ).

This project isn't hiding anything: it really is building a pipeline of relation values. It should be possible to re-implement it with a pipeline of tuples behind the scenes, but it was easier this way. It will never look more than superficially like jOOQ.

But if devs will put up with a DSL and two kinds of code generation in their build path, surely the kind of type tweaks I propose are small beer?

jOOQ's marketing seems to push it as a SQL-wrapping ORM -- I presume that's where the revenue comes from -- but it's also a framework for querying arbitrary containers. Most of the good bits seem to be here: https://github.com/jOOQ/jOOL

I'm the forum administrator and lead developer of Rel. Email me at dave@armchair.mb.ca with the Subject 'TTM Forum'. Download Rel from https://reldb.org
Quote from dandl on May 14, 2020, 1:47 pm
Quote from Erwin on May 14, 2020, 11:36 am
Quote from Dave Voorhis on May 14, 2020, 10:08 am
Quote from Erwin on May 14, 2020, 8:55 am
Quote from Dave Voorhis on May 10, 2020, 9:07 am
Quote from dandl on May 10, 2020, 8:04 am
  • Generalised aggregation as per RM Pre 6 is straightforward.  Andl has this, but I'm not aware of any other D that has.

Rel does. See https://reldb.org/c/wp-content/uploads/2016/06/User-Defined-Aggregate-Operators-in-Tutorial-D-and-Rel.pdf

 

SIRA_PRISE has too.  See http://shark.armchair.mb.ca/~erwin/languageandgrammar_0105.html

<ROaggregate> := AGGREGATE(<RelationalExpression>,(<AggregationDef>+))

Doesn't SIRA_PRISE have generalised transitive closure, too?

Yup.

<ROgtclose> := GTCLOSE(<RelationalExpression>,(<AttributeNamePair>+,),(<GTCloseDef>*))

And in this case there is indeed justification for including it since there's not really a way to take the "use a recursive operator" path.

Good to hear. That isn't really enough to work out what it can do: is it as general as while? (Which, by the way, is pretty much like SQL CTE RECURSIVE.)

And there's also this little curious coincidence :

<ROTransform> := TRANSFORM(<RelationalExpression>,(<TransformDef>*,))
<TransformDef
> := <AttributeName>[(<Expression>)]

Acknowledged. That one I did know about, and that influenced the choice of name. It may not be as 'pure' as the RA, but it sure can save on having to combine other operators to achieve a simple outcome.

The attribute name pairs control the tuple matching (which is on attribute value equality exclusively).  The GTCloseDefs control the computations for building "new" tuples in the closure.  Since while allows programming, so to speak, (and therefore theoretically supports any arbitrary predicate to be applied for tuples to match) it's unlikely to be as powerful as while, but otoh the programming in while makes it non-declarative and I've always wanted to stay away from that.

I'm baffled you seem to think that what is merely a syntactic shorthand for combinations of primitive operators of the RA, can in any way be "not as pure as the RA".  I have no idea what your agenda is with that choice of words.  It's RA.  Plain and simple.

Quote from Erwin on May 14, 2020, 11:36 am
Quote from Dave Voorhis on May 14, 2020, 10:08 am

Doesn't SIRA_PRISE have generalised transitive closure, too?

Yup.

<ROgtclose> := GTCLOSE(<RelationalExpression>,(<AttributeNamePair>+,),(<GTCloseDef>*))

I even ***use*** it internally :

Q_RTNAV=GTCLOSE(TRANSFORM(RESTRICT(JOIN(JOIN(UNION(PROJECT(EXTEND(JOIN(RECORDTYPE,HASHEDRECORDSPACE),BYKEYACCESSCOST(FLOAT(1.0))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)), \
                           PROJECT(EXTEND(JOIN(RECORDTYPE,CLUSTEREDRECORDSPACE,STORAGESPACE),BYKEYACCESSCOST(DIV(LN(TOFLOAT(PAGECOUNT)),LN(FLOAT(2.0))))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)) ), \
                                     GROUP(PROJECT(INDEXATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),LOCATTRIBUTES(ATTRIBUTENAME)), \
                                     GROUP(PROJECT(RECORDATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),RECATTRIBUTES(ATTRIBUTENAME))), \
                                RENAME(JOIN(UNION(PROJECT(EXTEND(JOIN(RECORDTYPE,HASHEDRECORDSPACE),BYKEYACCESSCOST(FLOAT(1.0))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)), \
                                          PROJECT(EXTEND(JOIN(RECORDTYPE,CLUSTEREDRECORDSPACE,STORAGESPACE),BYKEYACCESSCOST(DIV(LN(TOFLOAT(PAGECOUNT)),LN(FLOAT(2.0))))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)) ), \
                                            GROUP(PROJECT(INDEXATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),LOCATTRIBUTES(ATTRIBUTENAME)), \
                                            GROUP(PROJECT(RECORDATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),RECATTRIBUTES(ATTRIBUTENAME))) \
                                      ,(RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME,RECATTRIBUTES,TORECATTRIBUTES,LOCATTRIBUTES,TOLOCATTRIBUTES,BYKEYACCESSCOST,TOBYKEYACCESSCOST))) \
                 				  ,AND(CONTAINSR(RECATTRIBUTES,TOLOCATTRIBUTES),NOT(CONTAINSR(RECATTRIBUTES,TORECATTRIBUTES)),EQ(RELVARNAME,PARAMETER(RELVARNAME(NAME))),EQ(TORELVARNAME,PARAMETER(RELVARNAME(NAME))),NOT(EQ(RECORDTYPENAME,TORECORDTYPENAME)))), \
              (RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME,BYKEYACCESSCOST,TOBYKEYACCESSCOST,  \
                       ALLPATHATTRIBUTES(UNION(RECATTRIBUTES,TORECATTRIBUTES)) , \
                       TOTALPATHACCESSCOST(PLUS(BYKEYACCESSCOST,TOBYKEYACCESSCOST)) , \
                       FULLPATH(RELATION(HEADING(   RELVARNAME(NAME)RECORDTYPENAME(NAME)STEP(INT)STEPLOCATTRIBUTES(RELATION(HEADING(ATTRIBUTENAME(NAME))))) \
                                         BODY(TUPLE(RELVARNAME(RELVARNAME)RECORDTYPENAME(RECORDTYPENAME)STEP(INT(1))STEPLOCATTRIBUTES(LOCATTRIBUTES)) \
                                              TUPLE(RELVARNAME(RELVARNAME)RECORDTYPENAME(TORECORDTYPENAME)STEP(INT(2))STEPLOCATTRIBUTES(TOLOCATTRIBUTES))))) )) \
        , (RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME) \
                , (BYKEYACCESSCOST(__L__BYKEYACCESSCOST)  \
                   TOBYKEYACCESSCOST(__R__TOBYKEYACCESSCOST) \
                   ALLPATHATTRIBUTES(UNION(__L__ALLPATHATTRIBUTES,__R__ALLPATHATTRIBUTES)) \
                   TOTALPATHACCESSCOST(SUB(PLUS(__L__TOTALPATHACCESSCOST,__R__TOTALPATHACCESSCOST),__R__BYKEYACCESSCOST)) \
                   FULLPATH(UNION(__L__FULLPATH,RENAME(PROJECT(EXTEND(__R__FULLPATH,STEP2(SUB(PLUS(LENGTH(__L__FULLPATH),STEP),INT(1)))),(RELVARNAME,RECORDTYPENAME,STEP2,STEPLOCATTRIBUTES)),(STEP2,STEP)))) ))

The indentation mismatches with what I use in eclipse, and the input expression probably isn't decipherable anyway (when I wrote this I was wise enough to comment abundantly), but it's a pretty powerful thing.

One of the inputs is this relvar called 'HASHEDRECORDSPACE' and I'm currently replacing it with something new, which leads this query to believe that "there does not even exist any by-key access path to a given record type", and the effects on the time it takes to run even just the installation bootstrapping phase (creating the catalog) are immediately noticeable.

PS oh yes, and if I'm interested in the minimal access cost or some such then I could have wrapped an AGGREGATE around it to obtain

AGGREGATE(GTCLOSE(TRANSFORM( ...  exactly the three things OP wasn't aware any other D had, if I may be so cynical for a second.

Quote from Erwin on May 14, 2020, 7:51 pm
Quote from Erwin on May 14, 2020, 11:36 am
Quote from Dave Voorhis on May 14, 2020, 10:08 am

Doesn't SIRA_PRISE have generalised transitive closure, too?

Yup.

<ROgtclose> := GTCLOSE(<RelationalExpression>,(<AttributeNamePair>+,),(<GTCloseDef>*))

I even ***use*** it internally :

Q_RTNAV=GTCLOSE(TRANSFORM(RESTRICT(JOIN(JOIN(UNION(PROJECT(EXTEND(JOIN(RECORDTYPE,HASHEDRECORDSPACE),BYKEYACCESSCOST(FLOAT(1.0))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)), \
PROJECT(EXTEND(JOIN(RECORDTYPE,CLUSTEREDRECORDSPACE,STORAGESPACE),BYKEYACCESSCOST(DIV(LN(TOFLOAT(PAGECOUNT)),LN(FLOAT(2.0))))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)) ), \
GROUP(PROJECT(INDEXATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),LOCATTRIBUTES(ATTRIBUTENAME)), \
GROUP(PROJECT(RECORDATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),RECATTRIBUTES(ATTRIBUTENAME))), \
RENAME(JOIN(UNION(PROJECT(EXTEND(JOIN(RECORDTYPE,HASHEDRECORDSPACE),BYKEYACCESSCOST(FLOAT(1.0))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)), \
PROJECT(EXTEND(JOIN(RECORDTYPE,CLUSTEREDRECORDSPACE,STORAGESPACE),BYKEYACCESSCOST(DIV(LN(TOFLOAT(PAGECOUNT)),LN(FLOAT(2.0))))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)) ), \
GROUP(PROJECT(INDEXATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),LOCATTRIBUTES(ATTRIBUTENAME)), \
GROUP(PROJECT(RECORDATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),RECATTRIBUTES(ATTRIBUTENAME))) \
,(RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME,RECATTRIBUTES,TORECATTRIBUTES,LOCATTRIBUTES,TOLOCATTRIBUTES,BYKEYACCESSCOST,TOBYKEYACCESSCOST))) \
,AND(CONTAINSR(RECATTRIBUTES,TOLOCATTRIBUTES),NOT(CONTAINSR(RECATTRIBUTES,TORECATTRIBUTES)),EQ(RELVARNAME,PARAMETER(RELVARNAME(NAME))),EQ(TORELVARNAME,PARAMETER(RELVARNAME(NAME))),NOT(EQ(RECORDTYPENAME,TORECORDTYPENAME)))), \
(RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME,BYKEYACCESSCOST,TOBYKEYACCESSCOST, \
ALLPATHATTRIBUTES(UNION(RECATTRIBUTES,TORECATTRIBUTES)) , \
TOTALPATHACCESSCOST(PLUS(BYKEYACCESSCOST,TOBYKEYACCESSCOST)) , \
FULLPATH(RELATION(HEADING( RELVARNAME(NAME)RECORDTYPENAME(NAME)STEP(INT)STEPLOCATTRIBUTES(RELATION(HEADING(ATTRIBUTENAME(NAME))))) \
BODY(TUPLE(RELVARNAME(RELVARNAME)RECORDTYPENAME(RECORDTYPENAME)STEP(INT(1))STEPLOCATTRIBUTES(LOCATTRIBUTES)) \
TUPLE(RELVARNAME(RELVARNAME)RECORDTYPENAME(TORECORDTYPENAME)STEP(INT(2))STEPLOCATTRIBUTES(TOLOCATTRIBUTES))))) )) \
, (RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME) \
, (BYKEYACCESSCOST(__L__BYKEYACCESSCOST) \
TOBYKEYACCESSCOST(__R__TOBYKEYACCESSCOST) \
ALLPATHATTRIBUTES(UNION(__L__ALLPATHATTRIBUTES,__R__ALLPATHATTRIBUTES)) \
TOTALPATHACCESSCOST(SUB(PLUS(__L__TOTALPATHACCESSCOST,__R__TOTALPATHACCESSCOST),__R__BYKEYACCESSCOST)) \
FULLPATH(UNION(__L__FULLPATH,RENAME(PROJECT(EXTEND(__R__FULLPATH,STEP2(SUB(PLUS(LENGTH(__L__FULLPATH),STEP),INT(1)))),(RELVARNAME,RECORDTYPENAME,STEP2,STEPLOCATTRIBUTES)),(STEP2,STEP)))) ))
Q_RTNAV=GTCLOSE(TRANSFORM(RESTRICT(JOIN(JOIN(UNION(PROJECT(EXTEND(JOIN(RECORDTYPE,HASHEDRECORDSPACE),BYKEYACCESSCOST(FLOAT(1.0))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)), \ PROJECT(EXTEND(JOIN(RECORDTYPE,CLUSTEREDRECORDSPACE,STORAGESPACE),BYKEYACCESSCOST(DIV(LN(TOFLOAT(PAGECOUNT)),LN(FLOAT(2.0))))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)) ), \ GROUP(PROJECT(INDEXATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),LOCATTRIBUTES(ATTRIBUTENAME)), \ GROUP(PROJECT(RECORDATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),RECATTRIBUTES(ATTRIBUTENAME))), \ RENAME(JOIN(UNION(PROJECT(EXTEND(JOIN(RECORDTYPE,HASHEDRECORDSPACE),BYKEYACCESSCOST(FLOAT(1.0))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)), \ PROJECT(EXTEND(JOIN(RECORDTYPE,CLUSTEREDRECORDSPACE,STORAGESPACE),BYKEYACCESSCOST(DIV(LN(TOFLOAT(PAGECOUNT)),LN(FLOAT(2.0))))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)) ), \ GROUP(PROJECT(INDEXATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),LOCATTRIBUTES(ATTRIBUTENAME)), \ GROUP(PROJECT(RECORDATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),RECATTRIBUTES(ATTRIBUTENAME))) \ ,(RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME,RECATTRIBUTES,TORECATTRIBUTES,LOCATTRIBUTES,TOLOCATTRIBUTES,BYKEYACCESSCOST,TOBYKEYACCESSCOST))) \ ,AND(CONTAINSR(RECATTRIBUTES,TOLOCATTRIBUTES),NOT(CONTAINSR(RECATTRIBUTES,TORECATTRIBUTES)),EQ(RELVARNAME,PARAMETER(RELVARNAME(NAME))),EQ(TORELVARNAME,PARAMETER(RELVARNAME(NAME))),NOT(EQ(RECORDTYPENAME,TORECORDTYPENAME)))), \ (RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME,BYKEYACCESSCOST,TOBYKEYACCESSCOST, \ ALLPATHATTRIBUTES(UNION(RECATTRIBUTES,TORECATTRIBUTES)) , \ TOTALPATHACCESSCOST(PLUS(BYKEYACCESSCOST,TOBYKEYACCESSCOST)) , \ FULLPATH(RELATION(HEADING( RELVARNAME(NAME)RECORDTYPENAME(NAME)STEP(INT)STEPLOCATTRIBUTES(RELATION(HEADING(ATTRIBUTENAME(NAME))))) \ BODY(TUPLE(RELVARNAME(RELVARNAME)RECORDTYPENAME(RECORDTYPENAME)STEP(INT(1))STEPLOCATTRIBUTES(LOCATTRIBUTES)) \ TUPLE(RELVARNAME(RELVARNAME)RECORDTYPENAME(TORECORDTYPENAME)STEP(INT(2))STEPLOCATTRIBUTES(TOLOCATTRIBUTES))))) )) \ , (RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME) \ , (BYKEYACCESSCOST(__L__BYKEYACCESSCOST) \ TOBYKEYACCESSCOST(__R__TOBYKEYACCESSCOST) \ ALLPATHATTRIBUTES(UNION(__L__ALLPATHATTRIBUTES,__R__ALLPATHATTRIBUTES)) \ TOTALPATHACCESSCOST(SUB(PLUS(__L__TOTALPATHACCESSCOST,__R__TOTALPATHACCESSCOST),__R__BYKEYACCESSCOST)) \ FULLPATH(UNION(__L__FULLPATH,RENAME(PROJECT(EXTEND(__R__FULLPATH,STEP2(SUB(PLUS(LENGTH(__L__FULLPATH),STEP),INT(1)))),(RELVARNAME,RECORDTYPENAME,STEP2,STEPLOCATTRIBUTES)),(STEP2,STEP)))) ))
Q_RTNAV=GTCLOSE(TRANSFORM(RESTRICT(JOIN(JOIN(UNION(PROJECT(EXTEND(JOIN(RECORDTYPE,HASHEDRECORDSPACE),BYKEYACCESSCOST(FLOAT(1.0))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)), \
                           PROJECT(EXTEND(JOIN(RECORDTYPE,CLUSTEREDRECORDSPACE,STORAGESPACE),BYKEYACCESSCOST(DIV(LN(TOFLOAT(PAGECOUNT)),LN(FLOAT(2.0))))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)) ), \
                                     GROUP(PROJECT(INDEXATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),LOCATTRIBUTES(ATTRIBUTENAME)), \
                                     GROUP(PROJECT(RECORDATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),RECATTRIBUTES(ATTRIBUTENAME))), \
                                RENAME(JOIN(UNION(PROJECT(EXTEND(JOIN(RECORDTYPE,HASHEDRECORDSPACE),BYKEYACCESSCOST(FLOAT(1.0))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)), \
                                          PROJECT(EXTEND(JOIN(RECORDTYPE,CLUSTEREDRECORDSPACE,STORAGESPACE),BYKEYACCESSCOST(DIV(LN(TOFLOAT(PAGECOUNT)),LN(FLOAT(2.0))))),(RELVARNAME,RECORDTYPENAME,BYKEYACCESSCOST)) ), \
                                            GROUP(PROJECT(INDEXATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),LOCATTRIBUTES(ATTRIBUTENAME)), \
                                            GROUP(PROJECT(RECORDATTRIBUTE,(RELVARNAME,RECORDTYPENAME,ATTRIBUTENAME)),RECATTRIBUTES(ATTRIBUTENAME))) \
                                      ,(RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME,RECATTRIBUTES,TORECATTRIBUTES,LOCATTRIBUTES,TOLOCATTRIBUTES,BYKEYACCESSCOST,TOBYKEYACCESSCOST))) \
                 				  ,AND(CONTAINSR(RECATTRIBUTES,TOLOCATTRIBUTES),NOT(CONTAINSR(RECATTRIBUTES,TORECATTRIBUTES)),EQ(RELVARNAME,PARAMETER(RELVARNAME(NAME))),EQ(TORELVARNAME,PARAMETER(RELVARNAME(NAME))),NOT(EQ(RECORDTYPENAME,TORECORDTYPENAME)))), \
              (RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME,BYKEYACCESSCOST,TOBYKEYACCESSCOST,  \
                       ALLPATHATTRIBUTES(UNION(RECATTRIBUTES,TORECATTRIBUTES)) , \
                       TOTALPATHACCESSCOST(PLUS(BYKEYACCESSCOST,TOBYKEYACCESSCOST)) , \
                       FULLPATH(RELATION(HEADING(   RELVARNAME(NAME)RECORDTYPENAME(NAME)STEP(INT)STEPLOCATTRIBUTES(RELATION(HEADING(ATTRIBUTENAME(NAME))))) \
                                         BODY(TUPLE(RELVARNAME(RELVARNAME)RECORDTYPENAME(RECORDTYPENAME)STEP(INT(1))STEPLOCATTRIBUTES(LOCATTRIBUTES)) \
                                              TUPLE(RELVARNAME(RELVARNAME)RECORDTYPENAME(TORECORDTYPENAME)STEP(INT(2))STEPLOCATTRIBUTES(TOLOCATTRIBUTES))))) )) \
        , (RELVARNAME,TORELVARNAME,RECORDTYPENAME,TORECORDTYPENAME) \
                , (BYKEYACCESSCOST(__L__BYKEYACCESSCOST)  \
                   TOBYKEYACCESSCOST(__R__TOBYKEYACCESSCOST) \
                   ALLPATHATTRIBUTES(UNION(__L__ALLPATHATTRIBUTES,__R__ALLPATHATTRIBUTES)) \
                   TOTALPATHACCESSCOST(SUB(PLUS(__L__TOTALPATHACCESSCOST,__R__TOTALPATHACCESSCOST),__R__BYKEYACCESSCOST)) \
                   FULLPATH(UNION(__L__FULLPATH,RENAME(PROJECT(EXTEND(__R__FULLPATH,STEP2(SUB(PLUS(LENGTH(__L__FULLPATH),STEP),INT(1)))),(RELVARNAME,RECORDTYPENAME,STEP2,STEPLOCATTRIBUTES)),(STEP2,STEP)))) ))

That's horrific and beautiful in equal measure. It's like staring into the abyss, and having it stare back and wink.

I'm the forum administrator and lead developer of Rel. Email me at dave@armchair.mb.ca with the Subject 'TTM Forum'. Download Rel from https://reldb.org