Ogre.QueryDomain specific language for constructing queries.
Currently only a select query is supported.
Currently, the expression language permits construction arithmetic and logical expressions on the base types (int, float, str and bool).
type 'a t = 'a querylogical expression language, defined as
exp ::= str `string`
| int `int64`
| float `float`
| bool `bool`
| `'a attribute`.(`'b field`)
| `'b field`.[`int`]
| exp <bop> exp
| <uop> exp
bop ::= <aop> | <lop> | <cop>
uop ::= not
aop ::= + | -
lop ::= || | && | ==>
cop ::= < | > | = | <> | <= | >=In the grammar above, the names delimited with backticks represent types of OCaml values, that should be passed at these syntactic locations (sort of an unquoting), for example, str "hello" is an expression, as well as student.(gpa) assuming that student is a value of type 'a attribute and gpa is a field of type 'b field.
Not all expressions are well-formed, as they also must obey to the typing rules. The typing rules are simple (informally):
0. x <bop> y is wff if x and y are of the same type; 1. x.(y) is wff if y is a field of attribute x; 2. x <aop> y is wff if x and y are float or int; 3. not x is wff if x is bool 4. x <lop> y is wff if x and y are bool 5. x <cop> y has type bool.
join statement.
The join statement is a list of equality classes. Each equality class defines a query constraint, requiring all elements of the class to be equal. The elements of the class are field variables, constructed with the field function. There are two kinds of the field variables:
field y ~from:x.field y.A fully qualified variable matches only with the corresponding field expression, e.g., an equality class
[field teacher ~from:student; field id ~from:teacher]
emposes a constraint student.(teacher) = teacher.(id), and is roughly equivalent to the SQL's
INNER JOIN teacher ON student.id = teacher.id
Note: it is OK to use where clause instead of the join clause to join attributes, if it makes the query more readable. There is no performance penalty.
The unqualified variable matches with the same fields ignoring the attribute name, for example, an equality class field
classid, will impose an equality constraint on values from all classid fields of the selected attributes. Given a concrete select query:
select (from student $teacher) ~join:[[field classid]]a constraint student.(classid) = teacher.(classid) is constructed. Another way to construct the same selection is:
select (from student $teacher)
~where:student.(classid) = teacher.(classid)a selection of attributes.
The tables clause can be constructed using the following grammar:
tables ::= from attr | <tables> $ attr
In other words, there are two constructors, a prefix from
attr, and an infix attr1 $ attr2, e.g.,
from students $ teachers $ classes
select ~where ~join (from t1 t2 ... tm) selects attributes t1, t2, ..., tm, join them by the fields specified in the join clause, and filters those that satisfy the condition defined with the where clause.
Examples:
Select all students that has the GPA rate greater than 3.8.
select
~where:(student.(gpa) > float 3.8)
(from students)Select all students and their corresponding teachers, that have a GPA greater than 3.8 (assuming that teacher is a foreign key to the table of teachers).
select
~where:(student.(gpa) > float 3.8)
~join:[[field teacher ~from:student; field id ~from:teacher]]
(from students)You may notice, that the select query lacks the SQL's WHAT clause, i.e., it is not possible or needed to specify columns. The reason for this, is that the query used as a value that is passed to some command constructor, (e.g.,foreach), that can work with fields individually, e.g., the following is a complete correspondence of the SQL's:
SELECT name FROM students WHERE gpa > 3.5
foreach Query.(select
~where:(student.(gpa) > float 3.8)
(from students))
~f:(fun s -> return (Student.name s))It is nearly three times as long, but in return it is type-safe, and composable.
from attr adds an attribute attr to the query. An attribute can be referenced in the query if it occurs in the from clause. Otherwise the query is not well-formed.
attrs $ attr appends an attribute attr to the sequence of chosen attributes attrs.
field name creates an unqualified join variable. field name ~from:attr creates a qualified join variable.
See the join type description, for the explanation of the join expressions and joining.
module Array : sig ... endDefines a subscripting syntax for creating field variables.
module String : sig ... endDefines field subscripting syntax.
val str : string -> expstr x creates a string constant.
val int : int64 -> expint x creates an integer constant
val bool : bool -> expbool x creates a logic constant
val float : float -> expfloat x creates a real number constant.
x ==> y implication.
Be aware that the precedence of OCaml operator ==> is higher than a common precedence of the implication operator in mathematics.
That means, that an expression x && y ==> x && z is parsed as x && (y ==> x) && z.
The rule of the thumb is to always put parenthesis in an expression, that has an implication, as even if you're aware of the precedence issue, it is not known to a reader of your code, whether you were aware, or wrote this code by a mistake.