Skip to content

Commit

Permalink
support better error messages
Browse files Browse the repository at this point in the history
For #238.
  • Loading branch information
apocelipes committed Jun 23, 2024
1 parent 8d7ffbb commit f74d6fa
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 9 deletions.
10 changes: 5 additions & 5 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ func (c *conn) prepareStmt(cmd string) (*stmt, error) {

var s C.duckdb_prepared_statement
if state := C.duckdb_prepare(c.duckdbCon, cmdstr, &s); state == C.DuckDBError {
dbErr := C.GoString(C.duckdb_prepare_error(s))
dbErr := getDuckDBError(C.GoString(C.duckdb_prepare_error(s)))
C.duckdb_destroy_prepare(&s)
return nil, errors.New(dbErr)
return nil, dbErr
}

return &stmt{c: c, stmt: &s}, nil
Expand All @@ -175,7 +175,7 @@ func (c *conn) extractStmts(query string) (C.duckdb_extracted_statements, C.idx_
err := C.GoString(C.duckdb_extract_statements_error(stmts))
C.duckdb_destroy_extracted(&stmts)
if err != "" {
return nil, 0, errors.New(err)
return nil, 0, getDuckDBError(err)
}
return nil, 0, errors.New("no statements found")
}
Expand All @@ -186,9 +186,9 @@ func (c *conn) extractStmts(query string) (C.duckdb_extracted_statements, C.idx_
func (c *conn) prepareExtractedStmt(extractedStmts C.duckdb_extracted_statements, index C.idx_t) (*stmt, error) {
var s C.duckdb_prepared_statement
if state := C.duckdb_prepare_extracted_statement(c.duckdbCon, extractedStmts, index, &s); state == C.DuckDBError {
dbErr := C.GoString(C.duckdb_prepare_error(s))
dbErr := getDuckDBError(C.GoString(C.duckdb_prepare_error(s)))
C.duckdb_destroy_prepare(&s)
return nil, errors.New(dbErr)
return nil, dbErr
}

return &stmt{c: c, stmt: &s}, nil
Expand Down
124 changes: 124 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "C"
import (
"errors"
"fmt"
"strings"
)

func getError(errDriver error, err error) error {
Expand Down Expand Up @@ -80,3 +81,126 @@ var (
errConnect = errors.New("could not connect to database")
errCreateConfig = errors.New("could not create config for database")
)

type DuckDBErrorType int

const (
ErrorTypeInvalid DuckDBErrorType = iota // invalid type
ErrorTypeOutOfRange // value out of range error
ErrorTypeConversion // conversion/casting error
ErrorTypeUnknownType // unknown type
ErrorTypeDecimal // decimal related
ErrorTypeMismatchType // type mismatch
ErrorTypeDivideByZero // divide by 0
ErrorTypeObjectSize // object size exceeded
ErrorTypeInvalidType // incompatible for operation
ErrorTypeSerialization // serialization
ErrorTypeTransaction // transaction management
ErrorTypeNotImplemented // method not implemented
ErrorTypeExpression // expression parsing
ErrorTypeCatalog // catalog related
ErrorTypeParser // parser related
ErrorTypePlanner // planner related
ErrorTypeScheduler // scheduler related
ErrorTypeExecutor // executor related
ErrorTypeConstraint // constraint related
ErrorTypeIndex // index related
ErrorTypeStat // stat related
ErrorTypeConnection // connection related
ErrorTypeSyntax // syntax related
ErrorTypeSettings // settings related
ErrorTypeBinder // binder related
ErrorTypeNetwork // network related
ErrorTypeOptimizer // optimizer related
ErrorTypeNullPointer // nullptr exception
ErrorTypeIO // IO exception
ErrorTypeInterrupt // interrupt
ErrorTypeFatal // Fatal exceptions are non-recoverable, and render the entire DB in an unusable state
ErrorTypeInternal // Internal exceptions indicate something went wrong internally (i.e. bug in the code base)
ErrorTypeInvalidInput // Input or arguments error
ErrorTypeOutOfMemory // out of memory
ErrorTypePermission // insufficient permissions
ErrorTypeParameterNotResolved // parameter types could not be resolved
ErrorTypeParameterNotAllowed // parameter types not allowed
ErrorTypeDependency // dependency
ErrorTypeHTTP
ErrorTypeMissingExtension // Thrown when an extension is used but not loaded
ErrorTypeAutoLoad // Thrown when an extension is used but not loaded
ErrorTypeSequence
DuckDBExceptionUnknown DuckDBErrorType = -1
)

var exceptionPrefixMap = map[DuckDBErrorType]string{
ErrorTypeInvalid: "Invalid",
ErrorTypeOutOfRange: "Out of Range",
ErrorTypeConversion: "Conversion",
ErrorTypeUnknownType: "Unknown Type",
ErrorTypeDecimal: "Decimal",
ErrorTypeMismatchType: "Mismatch Type",
ErrorTypeDivideByZero: "Divide by Zero",
ErrorTypeObjectSize: "Object Size",
ErrorTypeInvalidType: "Invalid type",
ErrorTypeSerialization: "Serialization",
ErrorTypeTransaction: "TransactionContext",
ErrorTypeNotImplemented: "Not implemented",
ErrorTypeExpression: "Expression",
ErrorTypeCatalog: "Catalog",
ErrorTypeParser: "Parser",
ErrorTypePlanner: "Planner",
ErrorTypeScheduler: "Scheduler",
ErrorTypeExecutor: "Executor",
ErrorTypeConstraint: "Constraint",
ErrorTypeIndex: "Index",
ErrorTypeStat: "Stat",
ErrorTypeConnection: "Connection",
ErrorTypeSyntax: "Syntax",
ErrorTypeSettings: "Settings",
ErrorTypeBinder: "Binder",
ErrorTypeNetwork: "Network",
ErrorTypeOptimizer: "Optimizer",
ErrorTypeNullPointer: "NullPointer",
ErrorTypeIO: "IO",
ErrorTypeInterrupt: "INTERRUPT",
ErrorTypeFatal: "FATAL",
ErrorTypeInternal: "INTERNAL",
ErrorTypeInvalidInput: "Invalid Input",
ErrorTypeOutOfMemory: "Out of Memory",
ErrorTypePermission: "Permission",
ErrorTypeParameterNotResolved: "Parameter Not Resolved",
ErrorTypeParameterNotAllowed: "Parameter Not Allowed",
ErrorTypeDependency: "Dependency",
ErrorTypeHTTP: "HTTP",
ErrorTypeMissingExtension: "Missing Extension",
ErrorTypeAutoLoad: "Extension Autoloading",
ErrorTypeSequence: "Sequence",
}

type DuckDBError struct {
Type DuckDBErrorType
Msg string
}

func (de *DuckDBError) Error() string {
return de.Msg
}

func (de *DuckDBError) Is(err error) bool {
if derr, ok := err.(*DuckDBError); ok {
return derr.Msg == de.Msg
}
return false
}

func getDuckDBError(errMsg string) error {
errType := DuckDBExceptionUnknown
for k, v := range exceptionPrefixMap {
if strings.HasPrefix(errMsg, v+" Error") {
errType = k
break
}
}
return &DuckDBError{
Type: errType,
Msg: errMsg,
}
}
38 changes: 38 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,41 @@ func TestErrAPISetValue(t *testing.T) {
err := chunk.SetValue(1, 42, "hello")
testError(t, err, errAPI.Error(), columnCountErrMsg)
}

func TestGetDuckDBError(t *testing.T) {
testCases := []struct {
msg string
typ DuckDBErrorType
}{
{
msg: "",
typ: DuckDBExceptionUnknown,
},
{
msg: "Unknown",
typ: DuckDBExceptionUnknown,
},
{
msg: "Unknown Type Error: xxx",
typ: ErrorTypeUnknownType,
},
{
msg: "Constraint Error: Duplicate key \"key\" violates unique constraint. If this is an unexpected constraint violation please double check with the known index limitations section in our documentation (https://duckdb.org/docs/sql/indexes).",
typ: ErrorTypeConstraint,
},
{
msg: "Invalid Error: xxx",
typ: ErrorTypeInvalid,
},
{
msg: "Invalid Input Error: xxx",
typ: ErrorTypeInvalidInput,
},
}

for _, tc := range testCases {
err := getDuckDBError(tc.msg).(*DuckDBError)
require.Equal(t, tc.typ, err.Type)
require.Equal(t, tc.msg, err.Msg)
}
}
8 changes: 4 additions & 4 deletions statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ func (s *stmt) execute(ctx context.Context, args []driver.NamedValue) (*C.duckdb

var pendingRes C.duckdb_pending_result
if state := C.duckdb_pending_prepared(*s.stmt, &pendingRes); state == C.DuckDBError {
dbErr := C.GoString(C.duckdb_pending_error(pendingRes))
dbErr := getDuckDBError(C.GoString(C.duckdb_pending_error(pendingRes)))
C.duckdb_destroy_pending(&pendingRes)
return nil, errors.New(dbErr)
return nil, dbErr
}
defer C.duckdb_destroy_pending(&pendingRes)

Expand Down Expand Up @@ -254,9 +254,9 @@ func (s *stmt) execute(ctx context.Context, args []driver.NamedValue) (*C.duckdb
return nil, ctx.Err()
}

err := C.GoString(C.duckdb_result_error(&res))
err := getDuckDBError(C.GoString(C.duckdb_result_error(&res)))
C.duckdb_destroy_result(&res)
return nil, errors.New(err)
return nil, err
}

return &res, nil
Expand Down
3 changes: 3 additions & 0 deletions statement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ func TestPrepareWithError(t *testing.T) {
for _, tc := range testCases {
stmt, err := db.Prepare(tc.tpl)
if err != nil {
if _, ok := err.(*DuckDBError); !ok {
require.Fail(t, "error type is not DuckDBError")
}
require.ErrorContains(t, err, tc.err)
continue
}
Expand Down

0 comments on commit f74d6fa

Please sign in to comment.