[TOC]
Attributes also take a functional syntax, so that when the source code is normalized by the compiler
test.people
becomes
people(test)
and
test.people(@.age.$gte(30))
becomes
people(test $gte(age(@) 30))
and either form is acceptable as source.
Functional closures (e.g. for use with $filter
, $map
, $reduce
, and $fun
below) can be expressed in three ways:
- simply pass a function (i.e., not anonymously)
coll.$filter(foo)
- declare 0 or more arguments between square braces
[]
followed by the function body
coll.$filter([item idx]
idx.$mod(2).$when(item.foo)
)
- same as 2, but use
@
argument aliases instead of specifying the arguments
coll.$filter(
@1.$mod(2).$when(@.foo)
)
$so-much.$fun($sum)
$so-much.$fun([arg1 arg2] $sum(arg1 arg2))
$so-much.$fun($sum(@ @1 @2 @3))
test.buildings.$map(@.marketValue.$times(@.appreciationRate)).$reduce($sum)
A simple find looks like this
test.people
or to return only those documents that have a name
test.people.$filter(name)
calling a collection as a function is an alias for $filter
test.people(name)
test.people([person]
person.age.gte(30)
)
test.people(@.age.gte(30))
Example: get all the people who are known to be children. Since we have no metadata labeling people as children, we have to infer this from the parents' children
property:
test.people(children).$map(children).$cat
or simply
test.people.$map(children).$cat
The $cat
here is necessary because we don't want a list of lists, just a single list of children, so we concatenate the lists returned by each parent.children
at the end.
However, these queries only return references to the children (i.e., foreign keys), so to dereference the children
test.people.$map([person]
person.children.$map([child]
test.people([p] child.$eq(p.name)).$first
)
).$cat
which can get pretty unwieldy. The solution to this problem is composition. So we define $join
as a function (with arity overloading)
$join.$fun(
[self coll]
self.$join(coll coll.$name)
[self coll from]
self.$join(coll from _id)
[self coll from to]
self.from.$map(
coll(@.$eq(@@.to)).$first
)
)
so that we can join two collections more easily.
test.people.$map(
@.$join(test.people children name)
).$cat
Compare to SQL:
SELECT * FROM test.person
WHERE person.name in (
SELECT c.person_name FROM test.person p, test.child c
WHERE p.name=c.person_name
)
Compare to Mongodb (using the nodejs native client [not isolated]):
test.collection("people").find({
children: { $not: { $size: 0 } }
}).toArray(function(err, docs) {
var childList = docs.reduce(function(p1, p2) {
return p2.children.concat(p1.children || p1);
});
test.collection("people").find({
name: { $in: childList }
}).toArray(callbackDefinedElsewhere);
});
Compare using new FDB DocumentLayer $deref extension:
db.people.find({
children: {
$exists: true,
$not: {$size: 0}
}
}, {
children: { // overwrite
$deref: {
$coll: "test.people",
$from: {
$each: "children"
},
$to: "name" // defaults to "_id"
}
}
})
Say you have a list of buildings and you want to get the sum of their market value
test.buildings.$map(marketValue).$reduce($sum)
Or how about the number of buildings having 5 or more tenants?
test.buildings(@.tenants.$count.$gte(5)).$count
Now that we know the building count, get the tenants in each building
test.buildings(@.tenants.$count.$gte(5)).$map(
@.$join(test.people tenants name)
)
From the previous example, instead of just the tenants, get the buildings with the tenants, and an easily accessible tenant count for each building
test.buildings(@.tenants.$count.$gte(5)).$map(
@.$assoc(tenants @.$join(test.people tenants name)).$assoc(tenantCount @.tenants.$count)
)
One problem with this is that the tenant count is requested (and calculated?) twice for each filtered building. So use $let
to bind the result to a symbol to be used anywhere in the body.
test.buildings.$map(
$let([c @.tenants.$count]
c.$when(@.$assoc(tenants @.$join(test.people tenants name)).$assoc(tenantCount c))
)
)(@)
E.g., a simple pattern matching query using SPARQL:
SELECT ?friend ?item WHERE {
?person x:knows ?friend .
?friend x:transaction ?transaction .
?transaction x:item ?item .
}
Get a preconstructed projection instead of a table you have to reconstruct in application code
test.people.$map([person]
person.$join(test.people knows name)(transactions).$map([friend]
friend.$merge({
items: friend.join(test.transactions).$map([transaction]
transaction.$join(test.catalog items)
).$apply($cat)
})
)
)
create an index
db.coll.$index(index-name key1 key2 key3 ... )
test.buildings.$index("buildings-by-age-and-market-value" age marketValue)
explicitly query with an index
test.buildings.$index("buildings-by-age-and-market-value")([building] building.age.$eq(30))
test.people.$index("people-by-name")(@.name.$eq("Tom"))
; this is a comment
;; so is this