7.9.6. SQL-06 — Error Handling
dasSQLITE follows a uniform naming convention. Pick the variant by how you want errors and “no row” to surface at the call site:
Suffix |
Meaning |
|---|---|
|
Strict; panics on error (programmer-error default) |
|
|
|
|
The same convention extends to the _sql macro: _sql(chain) is
strict, _try_sql(chain) is the non-panicking Result sibling.
7.9.6.1. Opening: try_open_sqlite
Use try_open_sqlite when the path is user-supplied or otherwise
unreliable. The strict open_sqlite / with_sqlite forms panic
on the same failure:
var open_result <- try_open_sqlite(":memory:")
if (open_result |> is_err) {
to_log(LOG_ERROR, "could not open db: {open_result |> unwrap_err}\n")
return
}
var inscope db <- open_result._value
SqlRunner finalizes on scope exit (closes the SQLite handle), so
it must be var inscope and bound by move (<-). Plain
unwrap would fail because the type isn’t copyable.
7.9.6.2. Writes: try_insert
insert panics on a constraint violation. try_insert returns
Result<int64, string> carrying the libsqlite3 error message:
let first = db |> try_insert(User(Id = 1, Name = "alice"))
// first : Result<int64, string>; rowid on success
// Explicit collision --- strict insert() would abort here.
let dup = db |> try_insert(User(Id = 1, Name = "alice-dup"))
if (dup |> is_err) {
// dup |> unwrap_err == "insert step failed: UNIQUE constraint failed: ..."
}
7.9.6.3. Reads where 0 rows is fine: query_scalar_opt
When 0 rows is a normal outcome (key not in table, lookup miss), the
*_opt variant returns Option<T>. Combine with ?? for a
default:
let lookup = db |> query_scalar_opt(
"SELECT \"Name\" FROM \"Users\" WHERE \"Id\" = 42", type<string>)
to_log(LOG_INFO, "user 42 is {lookup ?? "unknown"}\n")
7.9.6.4. _try_sql: _sql that doesn’t panic
_try_sql is the non-panic sibling of _sql. Same chain
analyzer, same emitted SQL — the only difference is the runtime
helper used and the return type:
// _try_sql(... |> _first()) : Result<T, string>
// _try_sql(... |> _first_opt()) : Result<Option<T>, string>
// _try_sql(... |> count()) : Result<int64, string>
let res = _try_sql(db |> select_from(type<User>) |> _first())
if (res |> is_ok) {
let u = res |> unwrap
}
The _first vs _first_opt distinction still applies under
_try_sql: _first reports 0 rows as Err; _first_opt
reports them as ok(none). Pick the one that matches your domain.
7.9.6.5. Quick reference
Form |
When to use |
|---|---|
|
Strict open; panic on failure |
|
Open with handleable error |
|
Strict; panic on constraint violation |
|
Returns rowid on success or libsqlite3 errmsg |
|
Strict; panic on 0 rows |
|
|
|
Strict |
|
Non-panic sibling; same SQL, wrapped result |
See also
Full source: tutorials/sql/06-error_handling.das
Previous tutorial: SQL-05 — Parameter Binding
Next tutorial: SQL-07 — Anatomy of _sql