Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support better error messages #239

Merged
merged 5 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
123 changes: 123 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 @@ -78,3 +79,125 @@ 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 error
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
)

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

type DuckDBError struct {
Type DuckDBErrorType
Msg string
}
taniabogatsch marked this conversation as resolved.
Show resolved Hide resolved

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 := ErrorTypeInvalid
// find the end of the prefix ("<error-type> Error: ")
if idx := strings.Index(errMsg, ": "); idx != -1 {
if typ, ok := errorPrefixMap[errMsg[:idx]]; ok {
errType = typ
}
}
return &DuckDBError{
Type: errType,
Msg: errMsg,
}
}
86 changes: 86 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,89 @@ func TestErrAPISetValue(t *testing.T) {
err := chunk.SetValue(1, 42, "hello")
testError(t, err, errAPI.Error(), columnCountErrMsg)
}

func TestDuckDBErrors(t *testing.T) {
db := openDB(t)
defer db.Close()
createTable(db, t, `CREATE TABLE duckdberror_test(bar VARCHAR UNIQUE, baz INT32)`)
_, err := db.Exec("INSERT INTO duckdberror_test(bar, baz) VALUES('bar', 0)")
require.NoError(t, err)

testCases := []struct {
tpl string
errTyp DuckDBErrorType
}{
{
tpl: "SELECT * FROM not_exist WHERE baz=0",
errTyp: ErrorTypeCatalog,
},
{
tpl: "COPY duckdberror_test FROM 'test.json'",
errTyp: ErrorTypeCatalog,
},
{
tpl: "SELECT * FROM duckdberror_test WHERE col=?",
errTyp: ErrorTypeBinder,
},
{
tpl: "SELEC * FROM duckdberror_test baz=0",
errTyp: ErrorTypeParser,
},
{
tpl: "INSERT INTO duckdberror_test(bar, baz) VALUES('bar', 1)",
errTyp: ErrorTypeConstraint,
},
{
tpl: "INSERT INTO duckdberror_test(bar, baz) VALUES('foo', 18446744073709551615)",
errTyp: ErrorTypeConversion,
},
{
tpl: "INSTALL not_exist",
errTyp: ErrorTypeHTTP,
},
{
tpl: "LOAD not_exist",
errTyp: ErrorTypeIO,
},
}
for _, tc := range testCases {
_, err := db.Exec(tc.tpl)
de, ok := err.(*DuckDBError)
if !ok {
require.Fail(t, "error type is not DuckDBError", "tql: %s\ngot: %#v", tc.tpl, err)
}
require.Equal(t, de.Type, tc.errTyp, "tql: %s\nactual error msg: %s", tc.tpl, de.Msg)
}
}

func TestGetDuckDBError(t *testing.T) {
// only for the corner cases
testCases := []*DuckDBError{
{
Msg: "",
Type: ErrorTypeInvalid,
},
{
Msg: "Unknown",
Type: ErrorTypeInvalid,
},
{
Msg: "Error: xxx",
Type: ErrorTypeUnknownType,
},
// next two for the prefix testing
{
Msg: "Invalid Error: xxx",
Type: ErrorTypeInvalid,
},
{
Msg: "Invalid Input Error: xxx",
Type: ErrorTypeInvalidInput,
},
}

for _, tc := range testCases {
err := getDuckDBError(tc.Msg).(*DuckDBError)
require.Equal(t, tc, err)
}
}
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
Loading