Hello Josh,
> A has("age",29), for example, operates at a different level of abstraction
> than a
> has("city","Santa Fe") if "city" is a column in an "addresses" table.
So hasXXX() operators work on TTuples. Thus:
g.V().hasLabel(‘person’).has(‘age’,29)
g.V().hasLabel(‘address’).has(‘city’,’Santa Fe’)
..both work as a person-vertex and an address-vertex are TTuples. If these were
tables, then:
jdbc.db().values(‘people’).has(‘age’,29)
jdbc.db().values(‘addresses’).has(‘city’,’Santa Fe’)
…also works as both people and addresses are TTables which extend
TTuple<String,?>.
In summary, its its a TTuple, then hasXXX() is good go.
////////// IGNORE UNTIL AFTER READING NEXT SECTION //////////
*** SIDENOTE: A TTable (which is a TSequence) could have Symbol-based metadata.
Thus TTable.value(#label) -> “people.” If so, then
jdbc.db().hasLabel(“people”).has(“age”,29)
> At least, they
> are different if the data model allows for multi-properties,
> meta-properties, and hyper-edges. A property is something that can either
> be there, attached to an element, or not be there. There may also be more
> than one such property, and it may have other properties attached to it. A
> column of a table, on the other hand, is always there (even if its value is
> allowed to be null), always has a single value, and cannot have further
> properties attached.
1. Multi-properties.
Multi-properties works because if name references a TSequence, then its the
sequence that you analyze with has(). This is another reason why TSequence is
important. Its a reference to a “stream” so there isn’t another layer of
tuple-nesting.
// assume v[1] has name={marko,mrodriguez,markor}
g.V(1).value(‘name’) => TSequence<String>
g.V(1).values(‘name’) => marko, mrodriguez, markor
g.V(1).has(‘name’,’marko’) => v[1]
2. Meta-properties
// assume v[1] has name=[value:marko,creator:josh,timestamp:12303] // i.e. a
tuple value
g.V(1).value(‘name’) => TTuple<?,String> // doh!
g.V(1).value(‘name’).value(‘value’) => marko
g.V(1).value(‘name’).value(‘creator’) => josh
So things get screwy. — however, it only gets screwy when you mix your
“metadata” key/values with your “data” key/values. This is why I think TSymbols
are important. Imagine the following meta-property tuple for v[1]:
[#value:marko,creator:josh,timestamp:12303]
If you do g.V(1).value(‘name’), we could look to the value indexed by the
symbol #value, thus => “marko”.
If you do g.V(1).values(‘name’), you would get back a TSequence with a single
TTuple being the meta property.
If you do g.V(1).values(‘name’).value(), we could get the value indexed by the
symbol #value.
If you do g.V(1).values(‘name’).value(‘creator’), it will return the primitive
string “josh”.
I believe that the following symbols should be recommended for use across all
data structures.
#id, #label, #key, #value
…where id(), label(), key(), value() are tuple.get(Symbol). Other symbols for
use with propertygraph/ include:
#outE, #inV, #inE, #outV, #bothE, #bothV
> In order to simplify user queries, you can let has() and values() do double
> duty, but I still feel that there are lower-level operations at play, at a
> logical level even if not at a bytecode level. However, expressing the a
> traversal in terms of its lowest-level relational operations may also be
> useful for query optimization.
One thing that I’m doing, that perhaps you haven’t caught onto yet, is that I’m
not modeling everything in terms of “tables.” Each data structure is trying to
stay as pure to its conceptual model as possible. Thus, there are no “joins” in
property graphs as outE() references a TSequence<TEdge>, where TEdge is an
interface that extends TTuple. You can just walk without doing any type of
INNER JOIN. Now, if you model a property graph in a relational database, you
will have to strategize the bytecode accordingly! Just a heads up in case you
haven’t noticed that.
Thanks for your input,
Marko.
http://rredux.com <http://rredux.com/>
>
> Josh
>
>
>
> On Mon, Apr 29, 2019 at 7:34 AM Marko Rodriguez <[email protected]
> <mailto:[email protected]>>
> wrote:
>
>> Hi,
>>
>> *** This email is primarily for Josh (and Kuppitz). However, if others are
>> interested… ***
>>
>> So I did a lot of thinking this weekend about structure/ and this morning,
>> I prototyped both graph/ and rdbms/.
>>
>> This is the way I’m currently thinking of things:
>>
>> 1. There are 4 base types in structure/.
>> - Primitive: string, long, float, int, … (will constrain
>> these at some point).
>> - TTuple<K,V>: key/value map.
>> - TSequence<V>: an iterable of v objects.
>> - TSymbol: like Ruby, I think we need “enum-like” symbols
>> (e.g., #id, #label).
>>
>> 2. Every structure has a “root.”
>> - for graph its TGraph implements TSequence<TVertex>
>> - for rdbms its a TDatabase implements
>> TTuple<String,TTable>
>>
>> 3. Roots implement Structure and thus, are what is generated by
>> StructureFactory.mint().
>> - defined using withStructure().
>> - For graph, its accessible via V().
>> - For rdbms, its accessible via db().
>>
>> 4. There is a list of core instructions for dealing with these
>> base objects.
>> - value(K key): gets the TTuple value for the provided key.
>> - values(K key): gets an iterator of the value for the
>> provided key.
>> - entries(): gets an iterator of T2Tuple objects for the
>> incoming TTuple.
>> - hasXXX(A,B): various has()-based filters for looking
>> into a TTuple and a TSequence
>> - db()/V()/etc.: jump to the “root” of the withStructure()
>> structure.
>> - drop()/add(): behave as one would expect and thus.
>>
>> ————
>>
>> For RDBMS, we have three interfaces in rdbms/.
>> (machine/machine-core/structure/rdbms)
>>
>> 1. TDatabase implements TTuple<String,TTable> // the root
>> structure that indexes the tables.
>> 2. TTable implements TSequence<TRow<?>> // a table is a sequence
>> of rows
>> 3. TRow<V> implements TTuple<String,V>> // a row has string column
>> names
>>
>> I then created a new project at machine/structure/jdbc). The classes in
>> here implement the above rdbms/ interfaces/
>>
>> Here is an RDBMS session:
>>
>> final Machine machine = LocalMachine.open();
>> final TraversalSource jdbc =
>> Gremlin.traversal(machine).
>> withProcessor(PipesProcessor.class).
>> withStructure(JDBCStructure.class,
>> Map.of(JDBCStructure.JDBC_CONNECTION, "jdbc:h2:/tmp/test"));
>>
>> System.out.println(jdbc.db().toList());
>> System.out.println(jdbc.db().entries().toList());
>> System.out.println(jdbc.db().value("people").toList());
>> System.out.println(jdbc.db().values("people").toList());
>> System.out.println(jdbc.db().values("people").value("name").toList());
>> System.out.println(jdbc.db().values("people").entries().toList());
>>
>> This yields:
>>
>> [<database#conn1: url=jdbc:h2:/tmp/test user=>]
>> [PEOPLE:<table#PEOPLE>]
>> [<table#people>]
>> [<row#PEOPLE:1>, <row#PEOPLE:2>]
>> [marko, josh]
>> [NAME:marko, AGE:29, NAME:josh, AGE:32]
>>
>> The bytecode of the last query is:
>>
>> [db(<database#conn1: url=jdbc:h2:/tmp/test user=>), values(people),
>> entries]
>>
>> JDBCDatabase implements TDatabase, Structure.
>> *** JDBCDatabase is the root structure and is referenced by db()
>> *** (CRUCIAL POINT)
>>
>> Assume another table called ADDRESSES with two columns: name and city.
>>
>>
>> jdbc.db().values(“people”).as(“x”).db().values(“addresses”).has(“name”,eq(path(“x”).by(“name”))).value(“city”)
>>
>> The above is equivalent to:
>>
>> SELECT city FROM people,addresses WHERE people.name=addresses.name
>>
>> If you want to do an inner join (a product), you do this:
>>
>>
>> jdbc.db().values(“people”).as(“x”).db().values(“addresses”).has(“name”,eq(path(“x”).by(“name”))).as(“y”).path(“x”,”y")
>>
>> The above is equivalent to:
>>
>> SELECT * FROM addresses INNER JOIN people ON people.name=addresses.name
>>
>> NOTES:
>> 1. Instead of select(), we simply jump to the root via db() (or
>> V() for graph).
>> 2. Instead of project(), we simply use value() or values().
>> 3. Instead of select() being overloaded with by() join syntax, we
>> use has() and path().
>> - like TP3 we will be smart about dropping path() data
>> once its no longer referenced.
>> 4. We can also do LEFT and RIGHT JOINs (haven’t thought through
>> FULL OUTER JOIN yet).
>> - however, we don’t support ‘null' in TP so I don’t know
>> if we want to support these null-producing joins. ?
>>
>> LEFT JOIN:
>> * If an address doesn’t exist for the person, emit a “null”-filled
>> path.
>>
>> jdbc.db().values(“people”).as(“x”).
>> db().values(“addresses”).as(“y”).
>> choose(has(“name”,eq(path(“x”).by(“name”))),
>> identity(),
>> path(“y”).by(null).as(“y”)).
>> path(“x”,”y")
>>
>> SELECT * FROM addresses LEFT JOIN people ON people.name=addresses.name
>>
>> RIGHT JOIN:
>>
>> jdbc.db().values(“people”).as(“x”).
>> db().values(“addresses”).as(“y”).
>> choose(has(“name”,eq(path(“x”).by(“name”))),
>> identity(),
>> path(“x”).by(null).as(“x”)).
>> path(“x”,”y")
>>
>>
>> SUMMARY:
>>
>> There are no “low level” instructions. Everything is based on the standard
>> instructions that we know and love. Finally, if not apparent, the above
>> bytecode chunks would ultimately get strategized into a single SQL query
>> (breadth-first) instead of one-off queries (depth-first) to improve
>> performance.
>>
>> Neat?,
>> Marko.
>>
>> http://rredux.com <http://rredux.com/> <http://rredux.com/
>> <http://rredux.com/>>