UltipaDocs
Try Playground
  • Introduction
    • Quick Start
    • Configuration
    • Connection and Session
    • Executing Queries
    • Graph Management
    • Transactions
    • Data Operations
    • Bulk Import
    • Data Export
    • Health and Administration
    • Response Processing
    • Data Types
    • Error Handling
    • Quick Start
    • Configuration
    • Connection and Session
    • Executing Queries
    • Graph Management
    • Transactions
    • Data Operations
    • Bulk Import
    • Data Export
    • Health and Administration
    • Response Processing
    • Data Types
    • Error Handling
    • Quick Start
    • Configuration
    • Connection and Session
    • Executing Queries
    • Graph Management
    • Transactions
    • Data Operations
    • Bulk Import
    • Data Export
    • Health and Administration
    • Response Processing
    • Data Types
    • Error Handling
    • Quick Start
    • Configuration
    • Connection and Session
    • Executing Queries
    • Graph Management
    • Transactions
    • Data Operations
    • Bulk Import
    • Data Export
    • Health and Administration
    • Response Processing
    • Data Types
    • Error Handling
  1. Docs
  2. /
  3. Ultipa Drivers
  4. /
  5. Go

Error Handling

The GQLDB Go driver provides a comprehensive set of error variables and a custom error type for handling different failure scenarios.

Error Variables

All common errors are defined as package-level variables that can be checked using errors.Is():

Go
import (
    "errors"

    gqldb "github.com/gqldb/gqldb-go"
)

Configuration Errors

ErrorDescription
ErrNoHostsNo hosts configured in the client
ErrInvalidTimeoutInvalid timeout value specified
Go
config := &gqldb.Config{Hosts: []string{}}

client, err := gqldb.NewClient(config)
if err != nil {
    if errors.Is(err, gqldb.ErrNoHosts) {
        log.Println("You must configure at least one host")
    }
}

Connection Errors

ErrorDescription
ErrNoConnectionNo connection available
ErrConnectionClosedConnection has been closed
ErrConnectionFailedFailed to establish connection
ErrAllHostsFailedAll configured hosts are unreachable
ErrHealthCheckFailedHealth check failed
Go
func connectWithRetry(config *gqldb.Config, maxRetries int) (*gqldb.Client, error) {
    for i := 0; i < maxRetries; i++ {
        client, err := gqldb.NewClient(config)
        if err == nil {
            ctx := context.Background()
            _, err = client.Login(ctx, "user", "pass")
            if err == nil {
                return client, nil
            }
            client.Close()
        }

        if errors.Is(err, gqldb.ErrConnectionFailed) {
            log.Printf("Connection attempt %d failed, retrying...", i+1)
            time.Sleep(time.Duration(i+1) * time.Second)
            continue
        }

        if errors.Is(err, gqldb.ErrAllHostsFailed) {
            return nil, fmt.Errorf("all hosts unreachable: %w", err)
        }

        return nil, err
    }
    return nil, gqldb.ErrConnectionFailed
}

Session Errors

ErrorDescription
ErrNotLoggedInOperation requires authentication
ErrLoginFailedLogin failed (wrong credentials)
ErrLogoutFailedLogout operation failed
ErrSessionExpiredSession has expired
ErrInvalidSessionInvalid session
Go
func ensureLoggedIn(ctx context.Context, client *gqldb.Client) error {
    _, err := client.Gql(ctx, "MATCH (n) RETURN count(n)", nil)
    if err != nil {
        if errors.Is(err, gqldb.ErrNotLoggedIn) || errors.Is(err, gqldb.ErrSessionExpired) {
            log.Println("Session expired, re-authenticating...")
            _, err = client.Login(ctx, "user", "pass")
            return err
        }
        return err
    }
    return nil
}

Transaction Errors

ErrorDescription
ErrNoTransactionNo active transaction
ErrTransactionFailedTransaction operation failed
ErrTransactionNotFoundTransaction not found (may have timed out)
ErrTransactionAlreadyOpenTransaction already open
Go
func safeTransaction(ctx context.Context, client *gqldb.Client, graphName string, fn func(uint64) error) error {
    err := client.WithTransaction(ctx, graphName, false, fn)
    if err != nil {
        if errors.Is(err, gqldb.ErrTransactionFailed) {
            log.Printf("Transaction failed: %v", err)
        } else if errors.Is(err, gqldb.ErrTransactionNotFound) {
            log.Println("Transaction timed out before completion")
        }
        return err
    }
    return nil
}

Query Errors

ErrorDescription
ErrQueryFailedQuery execution failed
ErrQueryTimeoutQuery timed out
ErrInvalidQueryInvalid query syntax
ErrEmptyQueryQuery string is empty
Go
func executeQuery(ctx context.Context, client *gqldb.Client, query string) (*gqldb.Response, error) {
    response, err := client.Gql(ctx, query, nil)
    if err != nil {
        if errors.Is(err, gqldb.ErrEmptyQuery) {
            return nil, fmt.Errorf("query cannot be empty")
        }
        if errors.Is(err, gqldb.ErrQueryTimeout) {
            return nil, fmt.Errorf("query timed out")
        }
        if errors.Is(err, gqldb.ErrQueryFailed) {
            return nil, fmt.Errorf("query failed: %w", err)
        }
        return nil, err
    }
    return response, nil
}

Graph Errors

ErrorDescription
ErrGraphNotFoundGraph does not exist
ErrGraphExistsGraph already exists
ErrCreateGraphFailedFailed to create graph
ErrDropGraphFailedFailed to drop graph
Go
func ensureGraph(ctx context.Context, client *gqldb.Client, graphName string) error {
    _, err := client.GetGraphInfo(ctx, graphName)
    if err == nil {
        log.Printf("Graph %s exists", graphName)
        return nil
    }

    if !errors.Is(err, gqldb.ErrGraphNotFound) {
        return err
    }

    err = client.CreateGraph(ctx, graphName, gqldb.GraphTypeOpen, "")
    if err != nil {
        if errors.Is(err, gqldb.ErrGraphExists) {
            // Race condition: another process created it
            log.Printf("Graph %s was created by another process", graphName)
            return nil
        }
        return err
    }

    log.Printf("Created graph %s", graphName)
    return nil
}

Data Errors

ErrorDescription
ErrInsertFailedInsert operation failed
ErrDeleteFailedDelete operation failed
ErrExportFailedExport operation failed

Type Errors

ErrorDescription
ErrInvalidTypeInvalid type
ErrTypeConversionType conversion failed
ErrUnsupportedTypeUnsupported type

GqldbError Type

For more detailed error information, the driver provides the GqldbError type:

Go
type GqldbError struct {
    Code    int
    Message string
    Cause   error
}

func (e *GqldbError) Error() string
func (e *GqldbError) Unwrap() error

Creating Custom Errors

Go
err := gqldb.NewError(1001, "custom error message", originalError)

Unwrapping Errors

Go
var gqldbErr *gqldb.GqldbError
if errors.As(err, &gqldbErr) {
    log.Printf("GQLDB Error [%d]: %s", gqldbErr.Code, gqldbErr.Message)
    if gqldbErr.Cause != nil {
        log.Printf("Caused by: %v", gqldbErr.Cause)
    }
}

Error Handling Patterns

Comprehensive Error Handling

Go
func handleAllErrors(ctx context.Context, client *gqldb.Client) {
    _, err := client.Login(ctx, "user", "pass")
    if err != nil {
        var gqldbErr *gqldb.GqldbError
        if errors.As(err, &gqldbErr) {
            log.Printf("GQLDB Error [%s]: %s", reflect.TypeOf(err).String(), gqldbErr.Message)
            if gqldbErr.Cause != nil {
                log.Printf("Caused by: %v", gqldbErr.Cause)
            }
        } else {
            log.Printf("Unexpected error: %v", err)
        }
        return
    }

    _, err = client.Gql(ctx, "MATCH (n) RETURN n", nil)
    if err != nil {
        log.Printf("Query error: %v", err)
    }
}

Error Recovery with Retry

Go
func withRetry[T any](operation func() (T, error), maxRetries int, retryableErrors ...error) (T, error) {
    var lastErr error
    var zero T

    for attempt := 1; attempt <= maxRetries; attempt++ {
        result, err := operation()
        if err == nil {
            return result, nil
        }

        lastErr = err

        isRetryable := false
        for _, retryable := range retryableErrors {
            if errors.Is(err, retryable) {
                isRetryable = true
                break
            }
        }

        if !isRetryable || attempt == maxRetries {
            return zero, err
        }

        log.Printf("Attempt %d failed, retrying...", attempt)
        time.Sleep(time.Duration(attempt) * time.Second)
    }

    return zero, lastErr
}

// Usage
response, err := withRetry(
    func() (*gqldb.Response, error) {
        return client.Gql(ctx, "MATCH (n) RETURN n LIMIT 100", nil)
    },
    3,
    gqldb.ErrConnectionFailed,
)

Graceful Degradation

Go
func getDataWithFallback(ctx context.Context, client *gqldb.Client) (*gqldb.Response, error) {
    response, err := client.Gql(ctx, "MATCH (n:User) RETURN n", nil)
    if err != nil {
        var gqldbErr *gqldb.GqldbError
        if errors.As(err, &gqldbErr) && strings.Contains(gqldbErr.Message, "timeout") {
            // Fall back to a simpler query
            log.Println("Full query timed out, using limited query")
            return client.Gql(ctx, "MATCH (n:User) RETURN n LIMIT 100", nil)
        }
        return nil, err
    }
    return response, nil
}

Cleanup on Error

Go
func transactionWithCleanup(ctx context.Context, client *gqldb.Client, graphName string) error {
    tx, err := client.BeginTransaction(ctx, graphName, false, 60)
    if err != nil {
        return err
    }

    // Ensure rollback on panic or error
    committed := false
    defer func() {
        if !committed {
            if _, rollbackErr := client.Rollback(ctx, tx.ID); rollbackErr != nil {
                log.Printf("Rollback failed: %v", rollbackErr)
            }
        }
    }()

    config := &gqldb.QueryConfig{TransactionID: tx.ID}

    _, err = client.Gql(ctx, "INSERT (n:Test {_id: 't1'})", config)
    if err != nil {
        return err
    }

    _, err = client.Gql(ctx, "INSERT (n:Test {_id: 't2'})", config)
    if err != nil {
        return err
    }

    _, err = client.Commit(ctx, tx.ID)
    if err != nil {
        return err
    }
    committed = true

    return nil
}

Complete Example

Go
package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "time"

    gqldb "github.com/gqldb/gqldb-go"
)

func main() {
    config := gqldb.NewConfigBuilder().
        Hosts("192.168.1.100:9000").
        Timeout(30 * time.Second).
        Build()

    client, err := gqldb.NewClient(config)
    if err != nil {
        if errors.Is(err, gqldb.ErrNoHosts) {
            log.Fatal("No hosts configured")
        }
        log.Fatalf("Failed to create client: %v", err)
    }
    defer client.Close()

    ctx := context.Background()

    // Login with error handling
    _, err = client.Login(ctx, "admin", "password")
    if err != nil {
        if errors.Is(err, gqldb.ErrLoginFailed) {
            log.Fatal("Invalid credentials")
        }
        log.Fatalf("Login failed: %v", err)
    }
    fmt.Println("Logged in successfully")

    // Ensure graph exists
    graphName := "errorDemo"
    _, err = client.GetGraphInfo(ctx, graphName)
    if err != nil {
        if errors.Is(err, gqldb.ErrGraphNotFound) {
            err = client.CreateGraph(ctx, graphName, gqldb.GraphTypeOpen, "")
            if err != nil && !errors.Is(err, gqldb.ErrGraphExists) {
                log.Fatalf("Failed to create graph: %v", err)
            }
            fmt.Println("Created graph")
        } else {
            log.Fatalf("Failed to get graph info: %v", err)
        }
    }

    client.UseGraph(ctx, graphName)

    // Transaction with error handling
    err = client.WithTransaction(ctx, graphName, false, func(txID uint64) error {
        cfg := &gqldb.QueryConfig{TransactionID: txID}

        _, err := client.Gql(ctx,
            `INSERT (n:User {_id: 'u1', name: 'Alice'})`,
            cfg,
        )
        if err != nil {
            return err
        }

        // Simulate potential error
        if time.Now().UnixNano()%10 < 3 {
            return fmt.Errorf("random failure for demo")
        }

        return nil
    })

    if err != nil {
        if errors.Is(err, gqldb.ErrTransactionFailed) {
            fmt.Println("Transaction failed, changes rolled back")
        } else {
            fmt.Printf("Error during transaction: %v\n", err)
        }
    } else {
        fmt.Println("Transaction succeeded")
    }

    // Query with timeout handling
    queryConfig := &gqldb.QueryConfig{Timeout: 5}
    response, err := client.Gql(ctx, "MATCH (n) RETURN n", queryConfig)
    if err != nil {
        var gqldbErr *gqldb.GqldbError
        if errors.As(err, &gqldbErr) {
            if errors.Is(err, gqldb.ErrQueryTimeout) {
                fmt.Println("Query timed out, trying with limit")
                response, err = client.Gql(ctx, "MATCH (n) RETURN n LIMIT 10", nil)
                if err == nil {
                    fmt.Printf("Found %d results (limited)\n", response.RowCount)
                }
            } else {
                fmt.Printf("Query error: %s\n", gqldbErr.Message)
            }
        }
    } else {
        fmt.Printf("Found %d results\n", response.RowCount)
    }

    // Cleanup
    client.DropGraph(ctx, graphName, true)
    fmt.Println("Done")
}