The Challenge

In a recent side-project, I was tasked with establishing a connection to a legacy system utilizing a Microsoft Visual FoxPro database. The goal was to create a read-only method to extract data from this system into a new one.

The project was developed in Go, so my initial step was to find a suitable Go package. My search led me to go-dbase, which seemed to fit the bill perfectly.

However, upon testing the package, I encountered an unexpected hurdle. The package was throwing errors when attempting to gain read & write access to the database files. This was a problem because the project requirements specified that the access should be read-only, and the project’s environment only had read-only access to the database files.

The Solution

Given that the project’s requirement was solely to retrieve data from the database, read-only access to the files should have been sufficient. This led me to delve into the package code to identify where write access was being requested - one of the many benefits of open-source software. I quickly found the answer and implemented a read-only mode for further testing. After successful testing, I submitted Pull Request #62 to the original project, hoping it might be useful to others.

The Code

If you’re facing a similar challenge, the following code demonstrates how to use the new read-only mode to read a database:

package main

import (
    "encoding/json"
    "fmt"
    "github.com/Valentin-Kaiser/go-dbase/dbase"
)

func main() {
    // Open the database
    db, err := dbase.OpenDatabase(&dbase.Config{
        Filename:                          "./PATHTODATABASE/EXAMPLEDATABASE.DBC",
        TrimSpaces:                        true,
        ReadOnly:                          true,
        DisableConvertFilenameUnderscores: true,
    })
    if err != nil {
        panic(err)
    }
    // Defer the close and handle any errors
    defer func() {
        if cerr := db.Close(); err == nil {
            err = cerr
        }
    }()
    if err != nil {
        panic(err)
    }

    // Output tables info
    fmt.Printf("Tables:\n%v\n\n", db.Names())

    // Get a specific table
    tables := db.Tables()
    table := tables["EXAMPLETABLE"]

    // Output table info
    fmt.Printf(
        "Table info:\nName: %s Last modified: %v Column count: %v Record count: %v File size: %v\n\n",
        table.TableName(),
        table.Header().Modified(0),
        table.Header().ColumnsCount(),
        table.Header().RecordsCount(),
        table.Header().FileSize(),
    )

    // Output table column info
    fmt.Println("Table columns:")
    for _, column := range table.Columns() {
        fmt.Printf("Name: %v - Type: %v\n", column.Name(), column.Type())
    }

    // Get table rows
    fmt.Println("\nTable rows:")
    rows, err := table.Rows(true, true)
    if err != nil {
        panic(err)
    }
    for _, row := range rows {
        // Skip deleted rows
        if row.Deleted {
            continue
        }

        // Convert row to a map
        rowMap, err := row.ToMap()
        if err != nil {
            panic(err)
        }

        // Output row map
        s, _ := json.MarshalIndent(rowMap, "", "  ")
        fmt.Printf("Row: %s\n\n", s)
    }
}

References

https://github.com/Valentin-Kaiser/go-dbase