Packages

A very limited RDMS query builder library

Current section

Files

Jump to
queryb src queryb.gleam
Raw

src/queryb.gleam

import gleam/int
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/string
pub type QueryError {
ConstraintViolation(name: String, detail: String)
DatabaseError(detail: String)
}
pub type Query {
Select(
from: String,
columns: List(#(Option(String), String)),
order_by: #(String, Direction),
limit: Int,
offset: Int,
where: List(Condition),
)
Update(
table: String,
set: List(#(String, QueryValue)),
where: List(Condition),
)
Delete(from: String, where: List(Condition))
}
pub type Value {
TextValue(String)
IntValue(Int)
FloatValue(Float)
NullValue
}
pub type QueryValue {
RelationValue(table: Option(String), column: String)
Parameter(number: Int)
}
pub type Condition {
Equal(left: QueryValue, right: QueryValue)
StringContains(
string: QueryValue,
substring: QueryValue,
sensitivity: CaseSensitivity,
)
Or(List(Condition))
}
pub type CaseSensitivity {
CaseSensitive
CaseInsensitive
}
pub type Direction {
Ascending
Descending
}
pub fn to_postgresql(query: Query) -> String {
to_sql(query, query_parameter_prefix: "$", escaped_double_quote: "\"")
}
pub fn to_sql(
query: Query,
query_parameter_prefix param: String,
escaped_double_quote quote: String,
) -> String {
case query {
Delete(from: table, where: conditions) -> {
let query = "delete from " <> escape_name(table, quote)
conditions_to_sql(query, conditions, True, param, quote)
}
Update(table: table, set: updates, where: conditions) -> {
let query = "update " <> escape_name(table, quote)
let query = updates_to_sql(query, updates, True, param, quote)
conditions_to_sql(query, conditions, True, param, quote)
}
Select(
from: table,
columns: columns,
order_by: #(order_by_column, direction),
limit: limit,
offset: offset,
where: conditions,
) -> {
let query = select_columns("select ", columns, True, quote)
let query = query <> " from " <> escape_name(table, quote)
let query = conditions_to_sql(query, conditions, True, param, quote)
let query = query <> " order by " <> escape_name(order_by_column, quote)
let query = case direction {
Ascending -> query <> " asc"
Descending -> query <> " desc"
}
let query = query <> " limit " <> int.to_string(limit)
case offset {
0 -> query
_ -> query <> " offset " <> int.to_string(offset)
}
}
}
}
fn updates_to_sql(
query: String,
updates: List(#(String, QueryValue)),
first: Bool,
param: String,
quote: String,
) -> String {
case updates {
[] -> query
[#(name, value), ..updates] -> {
let query = case first {
True -> query <> " set "
False -> query <> ", "
}
let query = query <> escape_name(name, quote) <> " = "
let query = value_to_sql(query, value, param, quote)
updates_to_sql(query, updates, False, param, quote)
}
}
}
fn conditions_to_sql(
query: String,
conditions: List(Condition),
first: Bool,
param: String,
quote: String,
) -> String {
case conditions {
[] -> query
[condition, ..conditions] -> {
let query = case first {
True -> query <> " where "
False -> query <> " and "
}
let query = condition_to_sql(query, condition, param, quote)
conditions_to_sql(query, conditions, False, param, quote)
}
}
}
fn condition_to_sql(
query: String,
condition: Condition,
query_parameter_prefix param: String,
escaped_double_quote quote: String,
) -> String {
case condition {
Or(conditions) -> {
list.index_fold(conditions, query, fn(query, condition, i) {
let query = case i {
0 -> query
_ -> query <> " or "
}
condition_to_sql(query <> "(", condition, param, quote) <> ")"
})
}
Equal(left, right) -> {
let query = value_to_sql(query, left, param, quote) <> " = "
value_to_sql(query, right, param, quote)
}
StringContains(left, right, sensitivity) -> {
let query = value_to_sql(query, left, param, quote)
let query = case sensitivity {
CaseSensitive -> query <> " like '%'||"
CaseInsensitive -> query <> " ilike '%'||"
}
value_to_sql(query, right, param, quote) <> "||'%'"
}
}
}
fn value_to_sql(
query: String,
value: QueryValue,
param: String,
quote: String,
) -> String {
case value {
RelationValue(table: None, column: column) ->
query <> escape_name(column, quote)
RelationValue(table: Some(table), column: column) ->
query <> escape_name(table, quote) <> "." <> escape_name(column, quote)
Parameter(index) -> query <> param <> int.to_string(index)
}
}
fn select_columns(
query: String,
names: List(#(Option(String), String)),
first: Bool,
quote: String,
) -> String {
case names {
[] -> query
[#(table, column), ..names] if first -> {
let query = select_column(query, table, column, quote)
select_columns(query, names, False, quote)
}
[#(table, column), ..names] -> {
let query = select_column(query <> ", ", table, column, quote)
select_columns(query, names, False, quote)
}
}
}
fn select_column(
query: String,
table: Option(String),
column: String,
quote: String,
) -> String {
case table {
None -> query <> escape_name(column, quote)
Some(table) ->
query <> escape_name(table, quote) <> "." <> escape_name(column, quote)
}
}
fn escape_name(name: String, quote: String) -> String {
"\"" <> string.replace(name, "\"", quote) <> "\""
}