Filtering with Predicates
So far we matched patterns by specifying exact values like "Bulbasaur". Predicates let us filter rows using arbitrary truthy expressions.
A predicate clause is a function call wrapped in an extra pair of brackets:
Every variable used in the predicate must be bound somewhere in the query, not necessarily by an earlier clause. A variable bound nowhere is the real error:
Order still matters for two other reasons, though: data patterns run in the order written, so put the selective ones first for performance, and predicates read best right after the clause that binds their variables. Let's find all Pokemon with a speed stat above 100:
Move [(> ?speed 100)] to the top of the :where clauses, above the patterns that bind ?e and ?speed. The query still returns the same 17 Pokemon.
We can compare two bound variables against each other. Here are the Pokemon whose defense is strictly higher than their attack:
not= is a predicate too: [(not= ?a ?b)] keeps a row only if the two values differ. The next chapter puts it to use.
Predicates work on strings too, this site allows clojure.string/starts-with? and clojure.string/includes?. In a real Datomic app, any pure clojure.core function is available (except eval), along with any Java or Clojure function on the classpath of the process that runs the query. With the Client API that process is the server, not your application, so your own functions aren't available unless they are deployed there too. Here we find Pokemon whose name starts with "S":
Good to know: =, !=, <, <=, > and >= are range predicates. Datomic answers them with an index instead of checking every value (on Datomic Pro this only applies to attributes marked :db/index true or :db/unique; Datomic Cloud and Local, which this site runs on, index every attribute already), so they are much more efficient than the same test written with another function. Datomic's own built-in functions are also cheaper than your own, since they run inside the query engine. Our database is too small to notice, but this matters in a real one. Strings sort by character code, not dictionary order, so uppercase comes before lowercase. Every name here is capitalized, which is why the names starting with "S" are also the names from "S" up to, but not including, "T". That is the same 14 Pokemon as before:
Or find Pokemon whose name contains "saur":
Combine two predicates to find Pokemon whose name starts with "S" and whose speed is above 100. Or find Pokemon whose attack and defense are both above 80.
- Write a predicate clause to filter rows on any truthy expression, including a comparison between two bound variables.
- Recognize the "not bound in expression clause" error, and know every predicate variable must be bound somewhere in the query.
- Know that Datomic answers range predicates with an index, and that strings sort by character code, not dictionary order.