cquill/repo

Types

Configuration for batch operations

pub type BatchConfig {
  BatchConfig(max_batch_size: Int, use_transaction: Bool)
}

Constructors

  • BatchConfig(max_batch_size: Int, use_transaction: Bool)

    Arguments

    max_batch_size

    Maximum rows per batch (for chunking large operations)

    use_transaction

    Whether to wrap the operation in a transaction (for atomicity)

Specific constraint violation types

pub type ConstraintKind {
  UniqueConstraint(field: String)
  ForeignKeyConstraint(field: String, references: String)
  NotNullConstraint(field: String)
  CheckConstraint(name: String)
  UnknownConstraint(name: String)
}

Constructors

  • UniqueConstraint(field: String)

    Unique constraint (e.g., duplicate email)

  • ForeignKeyConstraint(field: String, references: String)

    Foreign key constraint (e.g., referenced record doesn’t exist)

  • NotNullConstraint(field: String)

    NOT NULL constraint (e.g., required field missing)

  • CheckConstraint(name: String)

    CHECK constraint (e.g., value out of allowed range)

  • UnknownConstraint(name: String)

    Unknown constraint type

User-friendly error type for repository operations. These errors are designed for application code to handle.

pub type RepoError {
  NotFound(schema: String, query_description: String)
  TooManyRows(expected: Int, got: Int)
  ConstraintError(kind: ConstraintKind, detail: String)
  ConnectionError(reason: String)
  QueryError(message: String)
  ValidationErrors(errors: List(ValidationError))
  NotSupported(operation: String)
  Timeout
}

Constructors

  • NotFound(schema: String, query_description: String)

    Record not found when one was expected

  • TooManyRows(expected: Int, got: Int)

    Multiple records found when expecting at most one

  • ConstraintError(kind: ConstraintKind, detail: String)

    Constraint violation during mutation

  • ConnectionError(reason: String)

    Database connection error

  • QueryError(message: String)

    Query execution error

  • ValidationErrors(errors: List(ValidationError))

    Validation errors (for future changeset integration)

  • NotSupported(operation: String)

    Operation not supported by the adapter

  • Timeout

    Timeout during query execution

Savepoint error type for the repo API

pub type RepoSavepointError(e) {
  SavepointUserAborted(e)
  SavepointAdapterError(error.AdapterError)
  SavepointCreationFailed(reason: String)
  SavepointReleaseFailed(reason: String)
  SavepointNotFound(name: String)
  NotInTransaction
}

Constructors

  • SavepointUserAborted(e)

    User’s operation returned an error, savepoint was rolled back

  • SavepointAdapterError(error.AdapterError)

    Adapter/database error during savepoint, savepoint was rolled back

  • SavepointCreationFailed(reason: String)

    Savepoint could not be created

  • SavepointReleaseFailed(reason: String)

    Savepoint could not be released

  • SavepointNotFound(name: String)

    Savepoint was not found (possibly released or never created)

  • NotInTransaction

    Cannot use savepoint outside of a transaction

Transaction error type for the repo API

pub type RepoTransactionError(e) {
  UserAborted(e)
  AdapterError(error.AdapterError)
  TransactionFailed(reason: String)
  CommitFailed(reason: String)
  RolledBack
  TransactionTimedOut
  SerializationConflict
}

Constructors

  • UserAborted(e)

    User’s operation returned an error, transaction was rolled back

  • AdapterError(error.AdapterError)

    Adapter/database error during transaction, transaction was rolled back

  • TransactionFailed(reason: String)

    Transaction failed to start or commit

  • CommitFailed(reason: String)

    Commit failed (transaction may have been rolled back)

  • RolledBack

    Transaction was explicitly rolled back

  • TransactionTimedOut

    Transaction timed out

  • SerializationConflict

    Serialization failure - concurrent transaction conflict (retry may succeed)

Validation error for changeset integration

pub type ValidationError {
  ValidationError(
    field: String,
    message: String,
    kind: ValidationKind,
  )
}

Constructors

  • ValidationError(
      field: String,
      message: String,
      kind: ValidationKind,
    )

Types of validation errors

pub type ValidationKind {
  Required
  Format
  Length
  Range
  Custom(name: String)
}

Constructors

  • Required
  • Format
  • Length
  • Range
  • Custom(name: String)

Values

pub fn all(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
  decoder: fn(row) -> Result(a, String),
) -> Result(List(a), RepoError)

Fetch all records matching a query.

Example

query.from(user_schema)
|> query.where(query.eq_bool("active", True))
|> repo.all(adapter, conn, _, fn(row) { decode_user(row) })
pub fn batch_delete(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  queries: List(adapter.CompiledQuery),
) -> Result(Int, RepoError)

Batch delete multiple records. Each query in the list represents one delete operation.

pub fn batch_delete_with_config(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  queries: List(adapter.CompiledQuery),
  config: BatchConfig,
) -> Result(Int, RepoError)

Batch delete with configuration options.

pub fn batch_insert(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  queries: List(adapter.CompiledQuery),
) -> Result(Int, RepoError)

Insert multiple records using compiled queries. Each query in the list represents one row to insert.

Example

let queries = [
  adapter.CompiledQuery(
    sql: "INSERT INTO users (email, name) VALUES ($1, $2)",
    params: [adapter.param_string("a@test.com"), adapter.param_string("Alice")],
    expected_columns: 0
  ),
  adapter.CompiledQuery(
    sql: "INSERT INTO users (email, name) VALUES ($1, $2)",
    params: [adapter.param_string("b@test.com"), adapter.param_string("Bob")],
    expected_columns: 0
  ),
]
repo.batch_insert(adapter, conn, queries)
pub fn batch_insert_with_config(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  queries: List(adapter.CompiledQuery),
  config: BatchConfig,
) -> Result(Int, RepoError)

Insert multiple records with configuration options.

pub fn batch_update(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  queries: List(adapter.CompiledQuery),
) -> Result(Int, RepoError)

Batch update multiple records. Each query in the list represents one update operation.

pub fn batch_update_with_config(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  queries: List(adapter.CompiledQuery),
  config: BatchConfig,
) -> Result(Int, RepoError)

Batch update with configuration options.

pub fn capabilities(
  adapter: adapter.Adapter(conn, row),
) -> adapter.AdapterCapabilities

Get adapter capabilities

pub fn count(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
  int_decoder: fn(row) -> Result(Int, String),
) -> Result(Int, RepoError)

Count records matching the query.

Example

query.from(user_schema)
|> query.where(query.eq_bool("active", True))
|> repo.count(adapter, conn, _, int_decoder)
pub fn default_batch_config() -> BatchConfig

Default batch configuration

pub fn delete(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
) -> Result(Int, RepoError)

Delete a record. Returns the number of deleted rows (0 or 1).

Example

let sql = "DELETE FROM users WHERE id = $1"
let params = [adapter.param_int(user_id)]
let compiled = adapter.CompiledQuery(sql: sql, params: params, expected_columns: 0)
repo.delete(adapter, conn, compiled)
pub fn delete_all(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
) -> Result(Int, RepoError)

Delete all records matching a query. Returns the number of deleted rows.

Example

let sql = "DELETE FROM sessions WHERE expires_at < $1"
let params = [adapter.param_string(now)]
let compiled = adapter.CompiledQuery(sql: sql, params: params, expected_columns: 0)
repo.delete_all(adapter, conn, compiled)
pub fn exists(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
) -> Result(Bool, RepoError)

Check if any records match the query.

Example

query.from(user_schema)
|> query.where(query.eq_string("email", email))
|> repo.exists(adapter, conn, _)
pub fn format_error(err: RepoError) -> String

Format a RepoError for display

pub fn format_savepoint_error(
  err: RepoSavepointError(e),
) -> String

Format a RepoSavepointError for display

pub fn from_adapter_error(
  err: error.AdapterError,
  context: String,
) -> RepoError

Convert an AdapterError to a RepoError with context

pub fn from_savepoint_error(
  err: error.SavepointError(e),
) -> RepoSavepointError(e)

Convert a SavepointError to a RepoSavepointError

pub fn get(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
  decoder: fn(row) -> Result(a, String),
) -> Result(a, RepoError)

Fetch a single record (Error if not found).

Unlike one, this returns an error if no record is found.

Example

query.from(user_schema)
|> query.where(query.eq_int("id", user_id))
|> repo.get(adapter, conn, _, user_decoder)
pub fn get_by_id(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  schema: schema.Schema,
  id_value: adapter.QueryParam,
  decoder: fn(row) -> Result(a, String),
) -> Result(a, RepoError)

Fetch a record by its primary key.

Example

repo.get_by_id(adapter, conn, user_schema, 123, user_decoder)
pub fn insert(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
  decoder: fn(row) -> Result(a, String),
) -> Result(a, RepoError)

Insert a new record and return the inserted record.

Example

let sql = "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *"
let params = [adapter.param_string(email), adapter.param_string(name)]
let compiled = adapter.CompiledQuery(sql: sql, params: params, expected_columns: 0)
repo.insert(adapter, conn, compiled, user_decoder)
pub fn insert_all(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
) -> Result(Int, RepoError)

Insert multiple records at once. Returns the total number of inserted rows.

Example

let sql = "INSERT INTO users (email, name) VALUES ($1, $2), ($3, $4)"
let params = [
  adapter.param_string("a@example.com"), adapter.param_string("Alice"),
  adapter.param_string("b@example.com"), adapter.param_string("Bob"),
]
let compiled = adapter.CompiledQuery(sql: sql, params: params, expected_columns: 0)
repo.insert_all(adapter, conn, compiled)
pub fn insert_no_return(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
) -> Result(Int, RepoError)

Insert a new record without returning data. Returns the number of affected rows (usually 1).

Example

let sql = "INSERT INTO users (email, name) VALUES ($1, $2)"
let params = [adapter.param_string(email), adapter.param_string(name)]
let compiled = adapter.CompiledQuery(sql: sql, params: params, expected_columns: 0)
repo.insert_no_return(adapter, conn, compiled)
pub fn is_connection_error(err: RepoError) -> Bool

Check if an error is a connection error

pub fn is_constraint_error(err: RepoError) -> Bool

Check if an error is a constraint violation

pub fn is_not_found(err: RepoError) -> Bool

Check if an error is a not-found error

pub fn is_not_in_transaction(err: RepoSavepointError(e)) -> Bool

Check if a savepoint error indicates we’re not in a transaction

pub fn is_recoverable(err: RepoError) -> Bool

Check if an error is recoverable (can be retried)

pub fn is_savepoint_not_found(err: RepoSavepointError(e)) -> Bool

Check if a savepoint error indicates the savepoint was not found

pub fn is_unique_violation(err: RepoError) -> Bool

Check if an error is a unique constraint violation

pub fn one(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
  decoder: fn(row) -> Result(a, String),
) -> Result(option.Option(a), RepoError)

Fetch a single record (None if not found).

Returns Ok(None) if no rows match, Ok(Some(record)) if exactly one matches, or Error if more than one row matches.

Example

query.from(user_schema)
|> query.where(query.eq_string("email", email))
|> repo.one(adapter, conn, _, user_decoder)
pub fn savepoint(
  name: String,
  connection: conn,
  operation: fn(conn) -> Result(a, e),
) -> Result(a, RepoSavepointError(e))

Execute a function within a named savepoint.

Savepoints allow partial rollback within a transaction. If the function returns Ok, the savepoint is released. If the function returns Error, changes since the savepoint are rolled back but the transaction continues.

This must be called within an active transaction.

Example

repo.transaction(adapter, conn, fn(tx_conn) {
  // Do some work...

  // Try an operation that might fail
  case repo.savepoint("attempt_1", tx_conn, fn(sp_conn) {
    repo.insert(adapter, sp_conn, risky_insert_query, decoder)
  }) {
    Ok(record) -> Ok(record)
    Error(_) -> {
      // The insert failed and was rolled back, but we can continue
      repo.insert(adapter, tx_conn, fallback_insert_query, decoder)
    }
  }
})
pub fn select_all_from(table: String) -> adapter.CompiledQuery

Create a simple SELECT * FROM table query

pub fn select_by_id(
  table: String,
  pk_column: String,
) -> adapter.CompiledQuery

Create a SELECT * FROM table WHERE pk = $1 query

pub fn supports_returning(
  adapter: adapter.Adapter(conn, row),
) -> Bool

Check if an adapter supports RETURNING clauses

pub fn supports_transactions(
  adapter: adapter.Adapter(conn, row),
) -> Bool

Check if an adapter supports transactions

pub fn transaction(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  operation: fn(conn) -> Result(a, e),
) -> Result(a, RepoTransactionError(e))

Execute a function within a transaction.

If the function returns Ok, the transaction is committed. If the function returns Error, the transaction is rolled back.

Example

repo.transaction(adapter, conn, fn(tx_conn) {
  use from_account <- result.try(
    repo.get_by_id(adapter, tx_conn, account_schema, from_id, account_decoder)
    |> result.map_error(fn(_) { "Account not found" })
  )
  use _ <- result.try(
    repo.update_all(adapter, tx_conn, debit_query)
    |> result.map_error(fn(_) { "Failed to debit" })
  )
  Ok(Nil)
})
pub fn update(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
  decoder: fn(row) -> Result(a, String),
) -> Result(a, RepoError)

Update a record and return the updated record.

Example

let sql = "UPDATE users SET name = $1 WHERE id = $2 RETURNING *"
let params = [adapter.param_string(new_name), adapter.param_int(user_id)]
let compiled = adapter.CompiledQuery(sql: sql, params: params, expected_columns: 0)
repo.update(adapter, conn, compiled, user_decoder)
pub fn update_all(
  adapter: adapter.Adapter(conn, row),
  connection: conn,
  compiled: adapter.CompiledQuery,
) -> Result(Int, RepoError)

Update all records matching a query. Returns the number of updated rows.

Example

let sql = "UPDATE users SET active = $1 WHERE last_login < $2"
let params = [adapter.param_bool(False), adapter.param_string(cutoff_date)]
let compiled = adapter.CompiledQuery(sql: sql, params: params, expected_columns: 0)
repo.update_all(adapter, conn, compiled)
Search Document