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
-
TimeoutTimeout 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)
-
NotInTransactionCannot 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)
-
RolledBackTransaction was explicitly rolled back
-
TransactionTimedOutTransaction timed out
-
SerializationConflictSerialization 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 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_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_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)