Current section
Files
Jump to
Current section
Files
src/based_pg.gleam
import based.{
type BasedAdapter, type BasedError, type Query, type Value, BasedAdapter,
BasedError, Query,
}
import gleam/dynamic.{type Dynamic}
import gleam/int
import gleam/list
import gleam/option.{Some}
import gleam/pgo.{type Connection, type QueryError, Returned}
import gleam/result
import gleam/string
pub type Config {
Config(
host: String,
port: Int,
database: String,
username: String,
password: String,
)
}
/// Returns a `BasedAdapter` that can be passed into `based.register`.
pub fn adapter(config: Config) -> BasedAdapter(Config, Connection, t) {
BasedAdapter(with_connection: with_connection, conf: config, service: execute)
}
fn with_connection(config: Config, callback: fn(Connection) -> t) -> t {
let conn = connect(config)
let result = callback(conn)
pgo.disconnect(conn)
result
}
fn execute(query: Query, conn: Connection) -> Result(List(Dynamic), BasedError) {
let Query(sql, args) = query
let values = args |> to_pgo_values
pgo.execute(sql, conn, values, dynamic.dynamic)
|> result.map(fn(returned) {
let Returned(_count, rows) = returned
rows
})
|> result.map_error(to_based_error)
}
// TODO: improve error handling
fn to_based_error(error: QueryError) -> BasedError {
case error {
pgo.ConstraintViolated(msg, constraint, _detail) ->
BasedError(code: "constraint_violated", name: constraint, message: msg)
pgo.PostgresqlError(code, name, message) -> BasedError(code, name, message)
pgo.UnexpectedArgumentCount(expected, got) ->
BasedError(
code: "unexpected_argument_count",
name: "",
message: "Expected "
<> int.to_string(expected)
<> ", got "
<> int.to_string(got),
)
pgo.UnexpectedArgumentType(expected, got) ->
BasedError(
code: "unexpected_argument_count",
name: "",
message: "Expected " <> expected <> ", got " <> got,
)
pgo.UnexpectedResultType(decode_errors) ->
BasedError(
code: "unexpected_result_type",
name: "",
message: decode_error_message(decode_errors),
)
pgo.ConnectionUnavailable ->
BasedError(code: "connection_unavailable", name: "", message: "")
}
}
fn decode_error_message(errors: dynamic.DecodeErrors) -> String {
let assert [dynamic.DecodeError(expected, actual, path), ..] = errors
let path = string.join(path, ".")
"Decoder failed, expected "
<> expected
<> ", got "
<> actual
<> " in "
<> path
}
fn connect(config: Config) -> Connection {
let Config(host, port, database, user, password) = config
let conn =
pgo.connect(
pgo.Config(
..pgo.default_config(),
host: host,
port: port,
database: database,
user: user,
password: Some(password),
pool_size: 5,
),
)
conn
}
fn to_pgo_values(values: List(Value)) -> List(pgo.Value) {
values
|> list.map(fn(value) {
case value {
based.String(val) -> pgo.text(val)
based.Int(val) -> pgo.int(val)
based.Float(val) -> pgo.float(val)
based.Bool(val) -> pgo.bool(val)
based.Null -> pgo.null()
}
})
}