Skip to content

Add DuckDB::Error#error_type#1382

Merged
suketa merged 1 commit into
mainfrom
feature/error-type
Jun 22, 2026
Merged

Add DuckDB::Error#error_type#1382
suketa merged 1 commit into
mainfrom
feature/error-type

Conversation

@suketa

@suketa suketa commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Expose the DuckDB error category as a Symbol on DuckDB::Error, so callers (notably the ActiveRecord adapter) can map failures to RecordNotUnique / NotNullViolation etc. without parsing error messages.

begin
  con.query('INSERT INTO t VALUES (1)') # duplicate primary key
rescue DuckDB::Error => e
  e.error_type # => :constraint
end

Design

  • A shared C helper rbduckdb_raise_result_error captures duckdb_result_error_type at the two result-error sites — raw Connection#query and prepared-statement execution — because the failed result is destroyed before any Ruby object exists.
  • C only reads the message + raw int and passes them to DuckDB::Error.new(message, error_type_id). The int→Symbol mapping, the nil rule, and the public #error_type all live in Ruby (Converter::IntToSym::ERROR_TYPES).
  • error_type is nil for errors not originating from a query result (internal failures, async path).

Known limitation

DuckDB groups unique / not-null / FK violations under a single :constraint type, so consumers still need the message for the sub-kind — error_type provides the first-level dispatch.

Tests

error_type_test.rb: :constraint via both raw-query and bind paths, :catalog for unknown table, nil for a plain DuckDB::Error. Full suite green.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Error instances now include an error_type method that returns the DuckDB error category as a Symbol (such as :constraint for constraint violations or :catalog for catalog-related errors), or nil for non-query-result-originating errors.
  • Tests

    • Added test coverage for error type classification, including constraint violations, catalog errors, and cases where error type is unavailable.

Expose the DuckDB error category as a Symbol (e.g. :constraint, :catalog,
:parser) on DuckDB::Error, or nil for errors not originating from a query
result. A shared C helper captures duckdb_result_error_type at the two
result-error sites (raw query + prepared statement execution) and hands the
message + raw id to DuckDB::Error.new; the int -> Symbol mapping lives in Ruby.

This lets the ActiveRecord adapter map failures to RecordNotUnique /
NotNullViolation etc. without parsing error messages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds DuckDB::Error#error_type, which returns the DuckDB error category as a Ruby Symbol (or nil). A new C helper rbduckdb_raise_result_error reads the error message and type id from a duckdb_result and raises an enriched DuckDB::Error; connection.c and prepared_statement.c are updated to call it. A new ERROR_TYPES constant and error_type_to_sym converter handle the integer-to-symbol mapping in Ruby.

Changes

DuckDB::Error#error_type feature

Layer / File(s) Summary
Ruby Error class and int-to-sym converter
lib/duckdb/error.rb, lib/duckdb/converter/int_to_sym.rb, lib/duckdb.rb, .rubocop.yml
DuckDB::Error is defined with an optional error_type_id parameter and an error_type accessor. IntToSym gains ERROR_TYPES and error_type_to_sym. The class is required at library load time and exempted from the module-length check.
C rbduckdb_raise_result_error helper and callers
ext/duckdb/error.h, ext/duckdb/error.c, ext/duckdb/connection.c, ext/duckdb/prepared_statement.c
rbduckdb_raise_result_error is declared NORETURN in the header and implemented to extract the error message and type id from duckdb_result, construct a DuckDB::Error instance, and raise it. connection.c and prepared_statement.c replace their inline rb_raise constructions with calls to this helper.
Tests and changelog
test/duckdb_test/error_type_test.rb, CHANGELOG.md
Four tests cover :constraint errors (query and bound-parameter paths), :catalog errors for unknown tables, and nil from a bare DuckDB::Error constructor. A changelog entry documents the new method.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Ruby caller
  participant conn_c as connection.c
  participant err_c as rbduckdb_raise_result_error
  participant DuckDBError as DuckDB::Error (Ruby)
  participant IntToSym as Converter::IntToSym

  Client->>conn_c: db.query(sql)
  conn_c->>conn_c: duckdb_query → DuckDBError result
  conn_c->>err_c: rbduckdb_raise_result_error(&result)
  err_c->>err_c: duckdb_result_error → message
  err_c->>err_c: duckdb_result_error_type → type_id
  err_c->>DuckDBError: new(message, type_id)
  err_c-->>Client: rb_exc_raise (NORETURN)
  Client->>DuckDBError: rescue → e.error_type
  DuckDBError->>IntToSym: error_type_to_sym(`@error_type_id`)
  IntToSym-->>DuckDBError: :constraint / :catalog / …
  DuckDBError-->>Client: Symbol
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • suketa/ruby-duckdb#884: Modifies the same prepared_statement_execute failure path in ext/duckdb/prepared_statement.c to change how the Ruby exception is constructed and raised.
  • suketa/ruby-duckdb#909: Also updates prepared_statement_execute in ext/duckdb/prepared_statement.c to alter how DuckDB error information is extracted and surfaced as a raised Ruby exception.

Poem

🐇 A query went wrong, said the duck with a frown,
But now every error wears a symbol as crown!
:constraint, :catalog — each gets its own name,
error_type returns it, no two errors the same.
The rabbit hops gleefully, typing away —
Typed exceptions have brightened the day! 🦆

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Add DuckDB::Error#error_type' is precise and directly describes the main change: introducing a new error_type method to the DuckDB::Error class. It accurately summarizes the primary feature being added.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/error-type

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/duckdb/converter/int_to_sym.rb`:
- Around line 127-131: The error_type_to_sym method only validates the upper
bound but does not check for negative values. In Ruby, negative indices access
the array from the end, so passing a negative value would silently return an
incorrect error type instead of raising an error. Add an additional bounds check
in the error_type_to_sym method to ensure the value is not negative (val < 0)
and raise a DuckDB::Error for any negative indices, treating them as invalid
error type IDs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f46376cb-4c2e-4868-a397-bcd0de3fe41b

📥 Commits

Reviewing files that changed from the base of the PR and between b9feac5 and ff842ee.

📒 Files selected for processing (10)
  • .rubocop.yml
  • CHANGELOG.md
  • ext/duckdb/connection.c
  • ext/duckdb/error.c
  • ext/duckdb/error.h
  • ext/duckdb/prepared_statement.c
  • lib/duckdb.rb
  • lib/duckdb/converter/int_to_sym.rb
  • lib/duckdb/error.rb
  • test/duckdb_test/error_type_test.rb

Comment on lines +127 to +131
def error_type_to_sym(val) # :nodoc:
raise DuckDB::Error, "Unknown error type: #{val}" if val >= ERROR_TYPES.size

ERROR_TYPES[val]
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify no lower-bound guard currently exists and inspect neighboring converter guards.
rg -n -C2 'def error_type_to_sym|Unknown error type|ERROR_TYPES\[val\]' lib/duckdb/converter/int_to_sym.rb

Repository: suketa/ruby-duckdb

Length of output: 282


🏁 Script executed:

# Check ERROR_TYPES constant and search for calls to error_type_to_sym
rg -n 'ERROR_TYPES\s*=' lib/duckdb/converter/int_to_sym.rb | head -5
rg -n 'error_type_to_sym' lib/duckdb/ --type rb

Repository: suketa/ruby-duckdb

Length of output: 134


🏁 Script executed:

# Check the full ERROR_TYPES definition to understand its scope
sed -n '79,123p' lib/duckdb/converter/int_to_sym.rb

Repository: suketa/ruby-duckdb

Length of output: 920


🏁 Script executed:

# Search for calls to error_type_to_sym throughout the codebase
rg 'error_type_to_sym' lib/ --type-list | head -20
rg -n 'error_type_to_sym' lib/

Repository: suketa/ruby-duckdb

Length of output: 653


🏁 Script executed:

# Check if there are any direct uses of ERROR_TYPES or if they come from C bindings
rg -n 'ERROR_TYPES' lib/duckdb/converter/int_to_sym.rb
rg -n 'DuckDB::Error' lib/duckdb/ | grep -E '\(.*error_type|error.*code'

Repository: suketa/ruby-duckdb

Length of output: 207


🏁 Script executed:

# Check error.rb to see where `@error_type_id` comes from
cat -n lib/duckdb/error.rb | head -50

Repository: suketa/ruby-duckdb

Length of output: 1141


🏁 Script executed:

# Search for C extension code that sets error_type_id
find lib/duckdb -name '*.rb' -exec grep -l 'error_type_id' {} \;
find ext -name '*.c' -o -name '*.h' 2>/dev/null | head -20
rg 'error_type_id|duckdb_error_type' ext/ 2>/dev/null || echo "Checking ext directory..."

Repository: suketa/ruby-duckdb

Length of output: 674


🏁 Script executed:

# Check if there's any documentation about valid error type range
rg -n 'error.*type.*id|negative|negative?' lib/duckdb/ --type ruby

Repository: suketa/ruby-duckdb

Length of output: 1075


🏁 Script executed:

# Check the C extension error handling code
cat -n ext/duckdb/error.c | grep -A 20 -B 5 'error_type_id'

Repository: suketa/ruby-duckdb

Length of output: 942


🏁 Script executed:

# Check DuckDB C API documentation or how error codes are generated
rg -n 'duckdb_error_type\|error_type' ext/duckdb/error.c | head -20

Repository: suketa/ruby-duckdb

Length of output: 44


🏁 Script executed:

# Search for DuckDB error type definitions or enums in comments/code
rg -n 'duckdb_error_type\|ErrorType\|error_type.*enum' ext/duckdb/ -A 3

Repository: suketa/ruby-duckdb

Length of output: 44


🏁 Script executed:

# Check if there's any validation or bounds information in existing code
rg -n 'ERROR_TYPES.size\|ERROR_TYPES.length' lib/duckdb/converter/int_to_sym.rb

Repository: suketa/ruby-duckdb

Length of output: 44


🏁 Script executed:

# Look for any tests or examples that might show valid error type ranges
find . -name '*test*' -o -name '*spec*' | xargs grep -l 'error_type' 2>/dev/null | head -5

Repository: suketa/ruby-duckdb

Length of output: 100


🏁 Script executed:

# Check the error type test file
cat -n ./test/duckdb_test/error_type_test.rb

Repository: suketa/ruby-duckdb

Length of output: 1504


🏁 Script executed:

# Check the DuckDB C header files for error type definitions
rg -n 'typedef.*error.*type|enum.*error' ext/duckdb/*.h | head -20

Repository: suketa/ruby-duckdb

Length of output: 44


🏁 Script executed:

# Check if duckdb_result_error_type can return negative values
rg -n 'duckdb_result_error_type\|error_type.*result' ext/duckdb/error.c

Repository: suketa/ruby-duckdb

Length of output: 44


🏁 Script executed:

# Check if there are any other bounds checks in the codebase similar to error_type_to_sym
rg -n 'if.*negative\?' lib/duckdb/ | grep -E 'guard|raise|return'

Repository: suketa/ruby-duckdb

Length of output: 379


Add bounds check for negative error type IDs.

Line 128 checks the upper bound but allows negative indices, which are valid in Ruby. This could cause error_type_to_sym(-1) to return :invalid_configuration instead of raising, silently misclassifying invalid IDs.

Suggested fix
       def error_type_to_sym(val) # :nodoc:
-        raise DuckDB::Error, "Unknown error type: #{val}" if val >= ERROR_TYPES.size
+        raise DuckDB::Error, "Unknown error type: #{val}" if val.negative? || val >= ERROR_TYPES.size

         ERROR_TYPES[val]
       end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def error_type_to_sym(val) # :nodoc:
raise DuckDB::Error, "Unknown error type: #{val}" if val >= ERROR_TYPES.size
ERROR_TYPES[val]
end
def error_type_to_sym(val) # :nodoc:
raise DuckDB::Error, "Unknown error type: #{val}" if val.negative? || val >= ERROR_TYPES.size
ERROR_TYPES[val]
end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/duckdb/converter/int_to_sym.rb` around lines 127 - 131, The
error_type_to_sym method only validates the upper bound but does not check for
negative values. In Ruby, negative indices access the array from the end, so
passing a negative value would silently return an incorrect error type instead
of raising an error. Add an additional bounds check in the error_type_to_sym
method to ensure the value is not negative (val < 0) and raise a DuckDB::Error
for any negative indices, treating them as invalid error type IDs.

@suketa suketa merged commit 7b169d6 into main Jun 22, 2026
39 checks passed
@suketa suketa deleted the feature/error-type branch June 22, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant