use "collections"
class ODBCErrorChain
"""
Accumulates error frames from ODBC operations.
The error chain maintains a configurable history of error events, allowing
inspection of what operations failed and why. It supports:
- Maximum frame limit with oldest-first eviction
- Optional auto-clear on prepare/finish operations
- Optional logging of successful operations for tracing
## Configuration
```pony
stmt.configure_error_chain(
max_frames = 50,
auto_clear = true,
log_success = false
)
```
## Usage
```pony
// Access the most recent error
match stmt.last_error()
| let err: ODBCErrorFrame val =>
env.err.print(err.string())
end
// Iterate over all errors
for err in stmt.error_chain().values() do
env.err.print(err.operation + ": " + err.message())
end
// Print formatted chain
env.err.print(stmt.error_chain().string())
```
"""
var _frames: Array[ODBCErrorFrame val] = Array[ODBCErrorFrame val]
var _max_frames: USize = 100
var _sequence: USize = 0
var _auto_clear: Bool = true
var _log_success: Bool = false
new create() =>
"""
Create a new error chain with default settings.
"""
None
fun ref push(frame: ODBCErrorFrame val) =>
"""
Add a frame to the chain, evicting the oldest if at capacity.
"""
if _frames.size() >= _max_frames then
try _frames.shift()? end
end
_frames.push(frame)
fun ref clear() =>
"""
Clear all stored frames. Sequence counter is preserved.
"""
_frames.clear()
fun size(): USize =>
"""
Number of currently stored frames.
"""
_frames.size()
fun values(): ArrayValues[ODBCErrorFrame val, this->Array[ODBCErrorFrame val]]^ =>
"""
Iterate over frames from oldest to newest.
"""
_frames.values()
fun last(): (ODBCErrorFrame val | None) =>
"""
Get the most recent error frame, or None if empty.
"""
try
_frames(_frames.size() - 1)?
else
None
end
fun first(): (ODBCErrorFrame val | None) =>
"""
Get the oldest error frame, or None if empty.
"""
try
_frames(0)?
else
None
end
fun apply(i: USize): ODBCErrorFrame val ? =>
"""
Get frame by index. Throws if out of bounds.
"""
_frames(i)?
fun string(): String val =>
"""
Format the entire chain for display.
Output format:
```
Error chain (2 errors):
1. [stmt] prepare at main.pony:41 - OK
2. [stmt] execute at main.pony:42 - 42S02: Table 'nonexistent' doesn't exist
```
"""
if _frames.size() == 0 then
return "Error chain (empty)"
end
let error_count = _count_errors()
let label = if error_count == 1 then "error" else "errors" end
var result: String ref = String
result.append("Error chain (" + error_count.string() + " " + label)
if _log_success then
result.append(", " + (_frames.size() - error_count).string() + " success frames")
end
result.append("):\n")
var idx: USize = 1
for frame in _frames.values() do
result.append(" " + idx.string() + ". " + frame.string() + "\n")
idx = idx + 1
end
result.clone()
fun _count_errors(): USize =>
"""
Count non-success frames.
"""
var count: USize = 0
for frame in _frames.values() do
if not frame.is_success then
count = count + 1
end
end
count
fun ref next_sequence(): USize =>
"""
Get and increment the sequence counter.
"""
let seq = _sequence
_sequence = _sequence + 1
seq
fun current_sequence(): USize =>
"""
Get the current sequence counter without incrementing.
"""
_sequence
fun ref set_max_frames(n: USize) =>
"""
Configure maximum number of stored frames.
When reduced, excess frames are evicted oldest-first.
"""
_max_frames = n
while _frames.size() > _max_frames do
try _frames.shift()? end
end
fun max_frames(): USize =>
"""
Get the current maximum frames setting.
"""
_max_frames
fun ref set_auto_clear(v: Bool) =>
"""
Configure auto-clear behavior.
When true, the chain is cleared on prepare()/finish() calls.
"""
_auto_clear = v
fun auto_clear(): Bool =>
"""
Get the current auto-clear setting.
"""
_auto_clear
fun ref set_log_success(v: Bool) =>
"""
Configure success logging.
When true, successful operations are also recorded in the chain.
"""
_log_success = v
fun log_success(): Bool =>
"""
Get the current log_success setting.
"""
_log_success
fun errors_only(): Array[ODBCErrorFrame val] val =>
"""
Return only error frames (not success frames).
"""
var result: Array[ODBCErrorFrame val] trn = recover trn Array[ODBCErrorFrame val] end
for frame in _frames.values() do
if not frame.is_success then
result.push(frame)
end
end
consume result