Skip to content

Instantly share code, notes, and snippets.

@yawaramin
Last active November 6, 2019 19:34
Show Gist options
  • Save yawaramin/552c25a23549f15556d54954e39f946d to your computer and use it in GitHub Desktop.
Save yawaramin/552c25a23549f15556d54954e39f946d to your computer and use it in GitHub Desktop.
Type-safe SQL query using phantom types to model query parts as state transitions
(*
An incomplete implementation of a type-safe SQL query in F#.
The idea is that a query is built up clause by clause, by representing
each additional clause being added on to the query as a state transition
between different types of queries. We capture these different types as
phantom types to make sure that only valid transitions (query clause
additions) as defined by us can be carried out.
The final result is a 'total query' that can be converted to a string,
executed, etc.
*)
type select = interface end
type groupable = interface end
type orderable = interface end
type join = interface end
type total = interface end
type from = interface
inherit groupable
inherit orderable
inherit total
end
type cond = interface
inherit groupable
inherit orderable
inherit total
end
type group_by = interface inherit orderable inherit total end
type order_by = interface inherit total end
type 'a t = private T of string
let private commalist strings = String.concat ", " strings
let select columns : select t =
columns |> commalist |> sprintf "select %s" |> T
let from table ((T t) : select t) : from t =
table |> sprintf "%s\nfrom %s" t |> T
let select_all_from table : from t = select ["*"] |> from table
let inner_join table ((T t) : from t) : join t =
table |> sprintf "%s\ninner join %s" t |> T
let left_outer_join table ((T t) : from t) : join t =
table |> sprintf "%s\nleft outer join %s" t |> T
let right_outer_join table ((T t) : from t) : join t =
table |> sprintf "%s\nright outer join %s" t |> T
let full_outer_join table ((T t) : from t) : join t =
table |> sprintf "%s\nfull outer join %s" t |> T
let on cond ((T t) : join t) : cond t =
cond |> sprintf "%s\non %s" t |> T
let where cond ((T t) : #total t) : cond t =
cond |> sprintf "%s\nwhere %s" t |> T
let group_by columns ((T t) : #groupable t) : group_by t =
columns |> commalist |> sprintf "%s\ngroup by %s" t |> T
let order_by columns ((T t) : #orderable t) : order_by t =
columns |> commalist |> sprintf "%s\norder by %s" t |> T
let to_string ((T t) : #total t) : string = t
(*
Examples:
> Sql.select_all_from "employees" |> Sql.to_string;;
val it : string = "select *
from employees"
> select ["p.name"; "d.name"]
- |> from "people as p"
- |> inner_join "departments as d"
- |> on "p.department_id = d.id"
- |> where "d.id = 100"
- |> order_by ["p.id"]
- |> to_string;;
val it : string =
"select p.name, d.name
from people as p
inner join departments as d
on p.department_id = d.id
where d.id = 100
order by p.id"
> select ["id"; "name"] |> to_string;;
select ["id"; "name"] |> to_string;;
-------------------------^^^^^^^^^
/Users/yamin1/Downloads/stdin(63,26): error FS0001: The type 'select' is not compatible with the type 'total'
*)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment