package ogre

  1. Overview
  2. Docs

Domain 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 query
type exp

logical 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.

type join

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:

  • A fully qualified field variable, defined with the expression field y ~from:x.
  • An unqualified field variable, defined as 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)
type 'a tables

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

val select : ?where:exp -> ?join:join list list -> 'a tables -> 'a t

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.

val from : ('a, _) attribute -> (('a -> 'r) -> 'r) tables

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.

val ($) : ('a -> 'b -> 'r) tables -> ('b, _) attribute -> ('a -> 'r) tables

attrs $ attr appends an attribute attr to the sequence of chosen attributes attrs.

val field : ?from:(_, _) attribute -> _ field -> join

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 ... end

Defines a subscripting syntax for creating field variables.

module String : sig ... end

Defines field subscripting syntax.

val str : string -> exp

str x creates a string constant.

val int : int64 -> exp

int x creates an integer constant

val bool : bool -> exp

bool x creates a logic constant

val float : float -> exp

float x creates a real number constant.

val (&&) : exp -> exp -> exp

x && y conjunction

val (||) : exp -> exp -> exp

x || y disjunction

val (==>) : exp -> exp -> exp

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.

val not : exp -> exp

not x logical negation.

val (<) : exp -> exp -> exp

x < y less than

val (>) : exp -> exp -> exp

x > y greater than

val (=) : exp -> exp -> exp

x = y equality

val (<>) : exp -> exp -> exp

x <> y nonequality

val (<=) : exp -> exp -> exp

x <= y less than or equal

val (>=) : exp -> exp -> exp

x >= y greater or equal

val (+) : exp -> exp -> exp

x + y summation

val (-) : exp -> exp -> exp

x - y subtracting