// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
// SQLite reports a result code for every failure and says nothing else: no SQLSTATE, no
// severity, no hint. The code is what there is to classify on, so the driver turns it into
// the same `ErrorKind` tree the wire backends reach through their SQLSTATE — using the
// table Python's `sqlite3` module uses for its exception classes, so that code written
// against one of the two reads the same in the other.
///|
/// `SQLITE_OK`: the call succeeded.
let sqlite_ok : Int32 = 0
///|
/// `SQLITE_ROW`: `sqlite3_step` has a row to read.
let sqlite_row : Int32 = 100
///|
/// `SQLITE_DONE`: `sqlite3_step` finished the program.
let sqlite_done : Int32 = 101
///|
/// Raise the failure SQLite last recorded on `db`, with the class its result code implies.
///
/// The extended code travels in `code` so a caller can tell `SQLITE_CONSTRAINT_UNIQUE`
/// from `SQLITE_CONSTRAINT_NOTNULL` without parsing the message; the class comes from the
/// primary code, which is the low byte.
fn raise_sqlite(db : Int64) -> Unit raise @moondb.DbError {
raise failure_of(ffi_extended_errcode(db), bytes_to_str(ffi_errmsg(db)))
}
///|
/// Raise a failure from a code an API call returned rather than one recorded on the
/// connection — `sqlite3_bind_*` reports SQLITE_RANGE that way, and has no message of its
/// own to go with it.
fn raise_code(code : Int32, message : String) -> Unit raise @moondb.DbError {
raise failure_of(code, message)
}
///|
/// The `DbError` a SQLite result code becomes. Kept separate from the raising so a test can
/// ask for the classification of a code without provoking it.
fn failure_of(code : Int32, message : String) -> @moondb.DbError {
@moondb.Server({
kind: classify_result_code(code),
sqlstate: None,
code: Some(code),
message,
detail: Some("SQLite result code " + code.to_string()),
hint: None,
severity: None,
})
}
///|
/// The [`@moondb.ErrorKind`] of a SQLite result code.
///
/// The branches are the ones `Modules/_sqlite/util.c` takes in CPython's
/// `get_exception_class`, mapped onto the kinds this library has:
///
/// * `SQLITE_INTERNAL`, `SQLITE_NOTFOUND` → `Internal`; `SQLITE_NOMEM` → `Internal` as
/// well, since there is no separate out-of-memory kind.
/// * `SQLITE_ERROR`, `SQLITE_PERM`, `SQLITE_ABORT`, `SQLITE_BUSY`, `SQLITE_LOCKED`,
/// `SQLITE_READONLY`, `SQLITE_INTERRUPT`, `SQLITE_IOERR`, `SQLITE_FULL`, `SQLITE_CANTOPEN`,
/// `SQLITE_PROTOCOL`, `SQLITE_EMPTY`, `SQLITE_SCHEMA` → `Operational`, which is Python's
/// `OperationalError` and therefore covers a syntax error too.
/// * `SQLITE_CORRUPT` → `Database`.
/// * `SQLITE_TOOBIG` → `Data`.
/// * `SQLITE_CONSTRAINT` and `SQLITE_MISMATCH` → `Integrity`.
/// * `SQLITE_MISUSE`, `SQLITE_RANGE` → `Interface`.
/// * everything else, `SQLITE_AUTH` and `SQLITE_NOTADB` included → `Database`.
fn classify_result_code(code : Int32) -> @moondb.ErrorKind {
let primary = code & 0xFF
match primary {
2 | 12 => @moondb.ErrorKind::Internal
7 => @moondb.ErrorKind::Internal
1 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 =>
@moondb.ErrorKind::Operational
11 => @moondb.ErrorKind::Database
18 => @moondb.ErrorKind::Data
19 | 20 => @moondb.ErrorKind::Integrity
21 | 25 => @moondb.ErrorKind::Interface
_ => @moondb.ErrorKind::Database
}
}