Quelg: All Examples


Table

For example, we consider the products table, which has columns of product ID (pid), name, and price (price), and the orders table, which has columns of order ID (oid), product ID (pid), and quantity (qty). We assume the following simple database schema in our implementation:

 val products : unit -> < pid:int; name:string; price:int > list
 val orders   : unit -> < oid:int; pid:int; qty:int > list 

The embedding has a bit of noise in the form of extra arguments of type unit, to delay the evaluation of conditional branches and to get around the value restriction.


Q1: Simple Query

From these tables, we consider a query for sales by grouping by oid. We call this query \(Q_1\), and in Quelg we can write:

\( Q_1 = \mathcal{G}_{({\rm oid}, \alpha)}({\rm for}~(p \leftarrow {\rm table}(“{\rm products}”)) \\ ~~~~~~~~~~~~~~~~~~~~~~~{\rm for}~(o \leftarrow {\rm table}(“{\rm orders}”)) \\ ~~~~~~~~~~~~~~~~~~~~~~~{\rm where}~(p.{\rm pid} = o.{\rm pid}) \\ ~~~~~~~~~~~~~~~~~~~~~~~{\rm yield}~\{{\rm oid} = o.{\rm oid}, {\rm sales} = p.{\rm price} * o.{\rm qty}\}) \\ ~~~~~~~~{\rm Where}~~\alpha = \{({\rm sales}, {\rm SUM}, {\rm sale\_sum})\} \)
A query written in Quelg like the above query is represented in our implementation code in OCaml as:

  module Q1(S:SYM_SCHEMA) = struct
    open S
    let table_orders   = table ("orders", orders ())
    let table_products = table ("products", products ())

    let key = ooid
    let alpha = [(osale, (string "sum"), (string "sale_sum"))]
    let query = foreach (fun () -> table_products) @@ fun p ->
                foreach (fun () -> table_orders) @@ fun o ->
                where ((pid p) =% (opid o)) @@ fun () ->
                yield @@ osales (oid o) ((price p) *% (qty o))

    let q1 = group key alpha query @@ fun v key -> q1_res key (sale_sum v)

    let observe = observe
  end 

We can evaluate the above to translate an SQL string by the FixGenSQL interpreter. In our implementation, the queries for grouping are transformed as subqueries for clarity.

let module M = Q1(FixGenSQL) in M.observe (fun () -> M.q1);;
(*
  - : < key : int; sale_sum : int > list Schema.FixGenSQL.obs =
  "SELECT x.oid AS key, SUM(x.sale) AS sale_sum 
   FROM (SELECT z.oid AS oid, y.price * z.qty AS sale 
         FROM products AS y, orders AS z 
         WHERE true AND y.pid = z.pid) AS x 
   WHERE true 
   GROUP BY x.oid"
*) 

Q2: Query with Nested Control Structures

We consider a query with Nested Control Structures. We call this query \(Q_2\), and in Quelg we can write:

\( Q'_2 = \mathcal{G}_{({\rm oid}, \alpha)}({\rm for}~(p \leftarrow {\rm table}(“{\rm products}”)) \\ ~~~~~~~~~~~~~~~~~~~~~~~{\rm for}~(o \leftarrow {\rm table}(“{\rm orders}”)) \\ ~~~~~~~~~~~~~~~~~~~~~~~{\rm where}~(p.{\rm pid} = o.{\rm pid}) \\ ~~~~~~~~~~~~~~~~~~~~~~~{\rm yield}~\{{\rm oid} = o.{\rm oid}, {\rm sales} = p.{\rm price} * o.{\rm qty}\}) \\ ~~~~~~~~{\rm Where}~~\alpha = \{({\rm sales}, {\rm SUM}, {\rm sale\_sum}), ({\rm qty}, {\rm SUM}, {\rm qty\_sum})\} \)
\( Q_2 = {\rm for}~(q \leftarrow Q'_2) \\ ~~~~~~~~~~{\rm yield}~\{{\rm oid} = q.{\rm oid}, {\rm average} = q.{\rm sale\_sum}/q.{\rm qty\_sum}\} \)
A query written in Quelg like the above query is represented in our implementation code in OCaml as:

 module Q2'(S:SYM_SCHEMA) = struct
   open S
   let table_orders   = table ("orders", orders ())
   let table_products = table ("products", products ())

   let key = qoid
   let alpha = [(qsale, (string "sum"), (string "sale_sum"));
                (qqty,  (string "sum"), (string "qty_sum"))]
   let q2' = group key alpha (foreach (fun () -> table_products) @@ fun p ->
                              foreach (fun () -> table_orders) @@ fun o ->
                              where ((pid p) =% (opid o)) @@ fun () ->
                              yield @@ qsales (oid o) ((price p) *% (qty o)) (qty o))
                   (fun v key -> q2'_res key (sale_sum v) (qty_sum v))
 end

 module Q2(S:SYM_SCHEMA) = struct
   open S
   module M = Q2'(S)

   let q2 = foreach (fun () -> M.q2') @@ fun q ->
            yield @@ q2_res (q2_key q) ((q2_sale_sum q) /% (q2_qty_sum q))

   let observe = observe
 end 

We can evaluate the above to translate an SQL string by the FixGenSQL interpreter as follows.

let module M = Q2(FixGenSQL) in M.observe (fun () -> M.q2);;
(*
  - : < average : int; key : int > list Schema.FixGenSQL.obs =
  "SELECT x.key AS key, x.sale_sum / x.qty_sum AS average 
   FROM (SELECT x.oid AS key, SUM(x.sale) AS sale_sum, SUM(x.qty) AS qty_sum 
         FROM (SELECT z.oid AS oid, y.price * z.qty AS sale, z.qty AS qty 
               FROM products AS y, orders AS z WHERE true AND y.pid = z.pid) AS x 
         WHERE true 
         GROUP BY x.oid) AS x 
   WHERE true"
*) 

Q3: Compose

We consider a query that combines two queries. We prepare two queries \(Q'_3\) and \(Q''_3\), and we call the query composed of this queries \(Q_3\), and in Quelg we can write:

\( Q'_3 = \lambda oid.~{\rm for}~(o \leftarrow {\rm table}(“{\rm orders}”)) \\ ~~~~~~~~~~~~~~~~~~~~~{\rm where}~(o.{\rm oid} = oid) \\ ~~~~~~~~~~~~~~~~~~~~~{\rm yield}~o \)
\( Q''_3 = \mathcal{G}_{({\rm oid}, \alpha)}({\rm for}~(p \leftarrow {\rm table}(“{\rm products}”)) \\ ~~~~~~~~~~~~~~~~~~~~~~~{\rm where}~(p.{\rm pid} = o.{\rm pid}) \\ ~~~~~~~~~~~~~~~~~~~~~~~{\rm yield}~\{{\rm oid} = o.{\rm oid}, {\rm sales} = p.{\rm price} * o.{\rm qty}\}) \\ \)
\( Q_3 = \lambda x.~\mathcal{G}_{({\rm oid}, \alpha)}({\rm for}~(y \leftarrow Q'_3~x)~Q''_3~y) \\ ~~~~~~~~{\rm Where}~~\alpha = \{({\rm sales}, {\rm SUM}, {\rm sale\_sum})\} \)
A query written in Quelg like the above query is represented in our implementation code in OCaml as:

 module Q3'(S:SYM_SCHEMA) = struct
   open S
   let table_orders = table ("orders", orders ())

   let q3' = lam (fun xoid ->
        foreach (fun () -> table_orders) @@ fun o ->
        where ((oid o) >% xoid) @@ fun () ->
        yield o)
 end

 module Q3''(S:SYM_SCHEMA) = struct
   open S
   let table_products = table ("products", products ())

   let q3'' = lam (fun o ->
        foreach (fun () -> table_products) @@ fun p ->
        where ((pid p) =% (opid o)) @@ fun () ->
        yield @@ osales (oid o) ((price p) *% (qty o)))
 end

 module Q3(S:SYM_SCHEMA) = struct
   open S
   module M1 = Q3'(S)
   module M2 = Q3''(S)

   let key = ooid
   let alpha = [(osale, (string "sum"), (string "sale_sum"))]
   let q3' = lam (fun x -> group key alpha
                               (foreach (fun () -> app M1.q3' x) @@ fun y ->
                                app M2.q3'' y) @@ fun v key -> q1_res key (sale_sum v))

   let q3 = app q3' (int 1)

   let observe = observe
 end 

We can evaluate the above to translate an SQL string by the FixGenSQL interpreter as follows.

let module M = Q3(FixGenSQL) in M.observe (fun () -> M.q3);;
(*
  - : < key : int; sale_sum : int > list Schema.FixGenSQL.obs =
  "SELECT x.oid AS key, SUM(x.sale) AS sale_sum 
   FROM (SELECT y.oid AS oid, z.price * y.qty AS sale 
         FROM orders AS y, products AS z 
         WHERE true AND y.oid > 1 AND z.pid = y.pid) AS x 
   WHERE true 
   GROUP BY x.oid"
*) 

Q4: Lambda abstraction

We consider a query with Lambda abstraction. We call this query \(Q_4\), and in Quelg we can write:

\( Q'_4 = \lambda oid.~\mathcal{G}_{({\rm oid}, \alpha)}~({\rm for}~(o \leftarrow {\rm table}(“{\rm orders}”)) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{\rm where}~(o.{\rm oid} = oid) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{\rm yield}~o) \\ ~~~~~~~~{\rm Where}~~\alpha = \{({\rm qty}, {\rm AVG}, {\rm qty\_avg})\} \)
\( Q_4 = Q'_4~~2 \)
A query written in Quelg like the above query is represented in our implementation code in OCaml as:

 module Q4'(S:SYM_SCHEMA) = struct
   open S
   let table_orders = table ("orders", orders ())
 
   let key = oid
   let alpha = [(qty, (string "avg"), (string "qty_avg"))]
   let q4' = lam (fun xoid ->
        group key alpha
        (foreach (fun () -> table_orders) @@ fun o ->
         where ((oid o) =% xoid) @@ fun () ->
         yield o) @@ fun v key -> q4_res key (qty_avg v))
 end

 module Q4(S:SYM_SCHEMA) = struct
   open S
   module M = Q4'(S)
   let q4 = app M.q4' (int 2)

   let observe = observe
 end 

We can evaluate the above to translate an SQL string by the FixGenSQL interpreter as follows.

let module M = Q4(FixGenSQL) in M.observe (fun () -> M.q4);;
(*
  - : < key : int; qty_avg : int > list Schema.FixGenSQL.obs =
  "SELECT x.oid AS key, AVG(x.qty) AS qty_avg 
   FROM (SELECT y.* 
         FROM orders AS y 
         WHERE true AND y.oid = 2) AS x 
   WHERE true 
   GROUP BY x.oid"
*) 

Q5: Predicate

We consider a query that is taken a predicate as the argument and is grouped the results. We call this query \(Q_5\), and in Quelg we can write:

\( Q'_5 = \lambda p.~\mathcal{G}_{({\rm oid}, \alpha)}({\rm for}~(o \leftarrow {\rm table}(“{\rm orders}”)) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{\rm where}~p(o.{\rm qty}) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{\rm yield}~o) \\ ~~~~~~~~{\rm Where}~~\alpha = \{({\rm qty}, {\rm COUNT}, {\rm qty\_count})\} \)
\( Q_5 = Q'_5~(\lambda x.~x~>~2) \)
A query written in Quelg like the above query is represented in our implementation code in OCaml as:

 module Q5'(S:SYM_SCHEMA) = struct
   open S
   let table_orders = table ("orders", orders ())

   let key = oid
   let alpha = [(qty, (string "count"), (string "qty_count"))]
   let q5' = fun p ->
        group key alpha
        (foreach (fun () -> table_orders) @@ fun o ->
         where (p (qty o)) @@ fun () ->
         yield o) @@ fun v key -> q5_res key (qty_count v)
 end

 module Q5(S:SYM_SCHEMA) = struct
   open S
   module M = Q5'(S)
   let q5 = M.q5' (fun x -> x >% (int 2))

   let observe = observe
 end 

We can evaluate the above to translate an SQL string by the FixGenSQL interpreter as follows.

let module M = Q5(FixGenSQL) in M.observe (fun () -> M.q5);;
(*
  - : < key : int; qty_count : int > list Schema.FixGenSQL.obs =
  "SELECT x.oid AS key, COUNT(x.qty) AS qty_count 
   FROM (SELECT y.* 
         FROM orders AS y 
         WHERE true AND y.qty > 2) AS x 
   WHERE true 
   GROUP BY x.oid"
*) 

Q6: Predicate + Nested Control Structures

We consider a query with predicate and Nested Control Structures. We call this query \(Q_6\), and in Quelg we can write:

\( Q'_6 = \lambda p.~{\rm for}~(g \leftarrow Q_1) \\ ~~~~~~~~~~~~~~~~~{\rm where}~p(g.{\rm sale\_sum}) \\ ~~~~~~~~~~~~~~~~~{\rm yield}~g \)
\( Q_6 = Q'_6~(\lambda x.~x~>~10000) \)
A query written in Quelg like the above query is represented in our implementation code in OCaml as:

 module Q6'(S:SYM_SCHEMA) = struct
   open S
   module M = Q1(S)
   let q6' = fun p ->
        foreach (fun () -> M.q1) @@ fun g ->
        where (p (q1_sale_sum g)) @@ fun () ->
        yield g
 end

 module Q6(S:SYM_SCHEMA) = struct
   open S
   module M = Q6'(S)
   let q6 = M.q6' (fun x -> x >% (int 10000))

   let observe = observe
 end 

We can evaluate the above to translate an SQL string by the FixGenSQL interpreter as follows.

let module M = Q6(FixGenSQL) in M.observe (fun () -> M.q6);;
(*
  - : < key : int; sale_sum : int > list Schema.FixGenSQL.obs =
  "SELECT x.* 
   FROM (SELECT x.oid AS key, SUM(x.sale) AS sale_sum 
         FROM (SELECT z.oid AS oid, y.price * z.qty AS sale 
               FROM products AS y, orders AS z 
               WHERE true AND y.pid = z.pid) AS x 
         WHERE true GROUP BY x.oid) AS x 
   WHERE true AND x.sale_sum > 10000"
*) 

Q7: Double Nested Control Structures

We consider a query with double Nested Control Structures, that is generated four subqueries. We call this query \(Q_7\), and in Quelg we can write:

\( Q'_7 = \mathcal{G}_{({\rm qty\_count}, \alpha)}({\rm for}~(x \leftarrow Q_5) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{\rm yield}~x) \\ ~~~~~~~~{\rm Where}~~\alpha = \{({\rm qty\_count}, {\rm COUNT}, {\rm count\_count})\} \)
\( Q_7 = {\rm for}~(g \leftarrow Q'_7) \\ ~~~~~~~~~~{\rm where}~(g.{\rm count\_count}~>~1) \\ ~~~~~~~~~~{\rm yield}~g \)
A query written in Quelg like the above query is represented in our implementation code in OCaml as:

 module Q7'(S:SYM_SCHEMA) = struct
   open S
   module M = Q5(S)

   let key = q5_qty_count
   let alpha = [(q5_qty_count, (string "count"), (string "count_count"))]
   let query = foreach (fun () -> M.q5) @@ fun x -> yield x

   let q7' = group key alpha query @@ fun v key -> q7_res key (count_count v)
 end

 module Q7(S:SYM_SCHEMA) = struct
   open S
   module M = Q7'(S)

   let q7 = foreach (fun () -> M.q7') @@ fun g ->
            where ((q7_count_count g) >% (int 1)) @@ fun () ->
            yield g

   let observe = observe
 end 

We can evaluate the above to translate an SQL string by the FixGenSQL interpreter as follows.

let module M = Q7(FixGenSQL) in M.observe (fun () -> M.q7);;
(*
  - : < key : int; count_count : int > list Schema.FixGenSQL.obs =
  "SELECT x.* 
   FROM (SELECT x.qty_count AS key, COUNT(x.qty_count) AS count_count 
         FROM (SELECT y.* 
               FROM (SELECT y.oid AS key, COUNT(y.qty) AS qty_count 
                     FROM (SELECT z.* 
                           FROM orders AS z 
                           WHERE true AND z.qty > 2) AS y 
                     WHERE true 
                     GROUP BY y.oid) AS y 
               WHERE true) AS x 
         WHERE true 
         GROUP BY x.qty_count) AS x 
   WHERE true AND x.count_count > 1"
*) 

Q8: Double Nested Control Structures + Normalize

We consider a query with double Nested Control Structures and using existing normalization rules. We call this query \(Q_8\), and in Quelg we can write:

\( Q'_8 = \mathcal{G}_{({\rm sale\_sum}, \alpha)}({\rm for}~(x \leftarrow Q_3) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{\rm yield}~x) \\ ~~~~~~~~{\rm Where}~~\alpha = \{({\rm sale\_sum}, {\rm COUNT}, {\rm sale\_count})\} \)
\( Q_8 = {\rm for}~(g \leftarrow Q'_8) \\ ~~~~~~~~~~{\rm where}~(g.{\rm sale\_count}~=~1) \\ ~~~~~~~~~~{\rm yield}~g \)
A query written in Quelg like the above query is represented in our implementation code in OCaml as:

 module Q8'(S:SYM_SCHEMA) = struct
   open S
   module M = Q3(S)

   let key = q3_sale_sum
   let alpha = [(q3_sale_sum, (string "count"), (string "sale_count"))]
   let query = foreach (fun () -> M.q3) @@ fun x -> yield x

   let q8' = group key alpha query @@ fun v key -> q8_res key (sale_count v)
 end

 module Q8(S:SYM_SCHEMA) = struct
   open S
   module M = Q8'(S)

   let q8 = foreach (fun () -> M.q8') @@ fun g ->
            where ((q8_sale_count g) =% (int 1)) @@ fun () ->
            yield g

   let observe = observe
 end 

We can evaluate the above to translate an SQL string by the FixGenSQL interpreter as follows.

let module M = Q8(FixGenSQL) in M.observe (fun () -> M.q8);;
(*
  - : < key : int; sale_count : int > list Schema.FixGenSQL.obs =
  "SELECT x.* 
   FROM (SELECT x.sale_sum AS key, COUNT(x.sale_sum) AS sale_count 
         FROM (SELECT y.* 
               FROM (SELECT y.oid AS key, SUM(y.sale) AS sale_sum 
                     FROM (SELECT z.oid AS oid, u.price * z.qty AS sale 
                           FROM orders AS z, products AS u 
                           WHERE true AND z.oid > 1 AND u.pid = z.pid) AS y 
                     WHERE true 
                     GROUP BY y.oid) AS y 
               WHERE true) AS x 
         WHERE true 
         GROUP BY x.sale_sum) AS x 
   WHERE true AND x.sale_count = 1"
*) 

Q9: Nested Data Structures

We consider a query with Nested Data Structures. We call this query \(Q_9\), and in Quelg we can write:

\( nestedData = {\rm for}~(c \leftarrow {\rm table}(“{\rm classes}”)) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~{\rm yield}~\{{\rm class} = c.{\rm class}, {\rm students} = \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\mathcal{G}_{({\rm name}, \alpha)}({\rm for}~(s \leftarrow {\rm table}(“{\rm students}”)) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{\rm for}~(t \leftarrow {\rm table}(“{\rm tests}”)) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{\rm where}~(s.{\rm name} = t.{\rm name}) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{\rm yield}~\{{\rm name} = t.{\rm name}, {\rm score} = t.{\rm score}\})\} \\ ~~~~~~~~~~~~~~~~{\rm Where}~~\alpha = \{({\rm score}, {\rm AVG}, {\rm score\_avg})\} \)
\( Q_9 = \mathcal{G}_{({\rm class}, \alpha)}({\rm for}~(x \leftarrow nestedData) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~{\rm for}~(y \leftarrow x.{\rm students}) \\ ~~~~~~~~~~~~~~~~~~~~~~~~~{\rm yield}~\{{\rm class} = x.{\rm class}, {\rm score} = y.{\rm score}\}) \\ ~~~~~~~~~~{\rm Where}~~\alpha = \{({\rm score}, {\rm AVG}, {\rm score\_avg})\} \)
A query written in Quelg like the above query is represented in our implementation code in OCaml as:

 module NestedData(S:SYM_SCHEMA) = struct
   open S
   let table_classes = table ("classes", table_classes ())
   let table_students = table ("students", table_students ())
   let table_tests = table ("tests", table_tests ())

   let key = test_name
   let alpha = [(test_score, (string "avg"), (string "score_avg"))]

   let nestedData = foreach (fun () -> table_classes) @@ fun c ->
                    yield @@ nested_res (cclass c) (group key alpha (foreach (fun () -> table_students) @@ fun s ->
                                  foreach (fun () -> table_tests) @@ fun t ->
                                  where ((stname s) =@ (tname t)) @@ fun () ->
                                  yield @@ test (tname t) (tscore t)) (fun v key -> student_res key (score_avg v)))
 end

 module Q9(S:SYM_SCHEMA) = struct
   open S
   module M = NestedData(S)

   let key = scores_class
   let alpha = [(scores_score, (string "avg"), (string "score_avg"))]
   let q9 = group key alpha (foreach (fun () -> M.nestedData) @@ fun x ->
                             foreach (fun () -> (nested_student x)) @@ fun y ->
                             yield @@ scores (nested_class x) (q9_score_avg y))
                 (fun v key -> q9_res key (score_avg v))

   let observe = observe
 end 

We can evaluate the above to translate an SQL string by the FixGenSQL interpreter as follows.

let module M = Q8(FixGenSQL) in M.observe (fun () -> M.q8);;
(*
  - : < key : int; score_avg : int > list Schema.FixGenSQL.obs =
  "SELECT x.class AS key, AVG(x.score) AS score_avg 
   FROM (SELECT y.class AS class, z.score_avg AS score 
         FROM classes AS y, (SELECT z.name AS key, AVG(z.score) AS score_avg 
                             FROM (SELECT v.name AS name, v.score AS score 
                                   FROM students AS u, tests AS v 
                                   WHERE true AND u.name = v.name) AS z 
                             WHERE true 
                             GROUP BY z.name) AS z 
         WHERE true) AS x 
   WHERE true 
   GROUP BY x.class"
*)