DBF (dbf_ex v0.2.1)

Copy Markdown

Read FoxBase and dBASE DBF files.

DBFex provides read-only, positional access to records. For ordinary reads, prefer with_open/2 or with_open/3; it owns the database resource for the duration of the callback and closes it automatically:

DBF.with_open("customers.dbf", fn db ->
  Enum.to_list(db)
end)

Records and enumeration

An open DBF.Database implements Enumerable. Elements retain the physical record status and have one of these forms:

{:record, %{"NAME" => "Ada"}}
{:deleted_record, %{"NAME" => "Grace"}}
{:error, %DBF.DatabaseError{}}

Enumeration includes active and deleted records in file order. If decoding a record fails, the error tuple is emitted as the final element. Use get/2 for zero-based random access to a single physical record.

Options

The same options are accepted by open/2, open!/2, and with_open/3:

  • :memo_file - an explicit DBT path, or nil for automatic companion discovery. Defaults to nil.
  • :numeric - :float for compatible float results, or :exact for integers and Decimal values. Defaults to :float.
  • :encoding - :auto, :raw, :windows_1251, or :windows_1252. Defaults to :auto.
  • :encoding_errors - :strict, :replace, or :raw. Defaults to :raw.

For example, to read exact numeric values and require valid Windows-1252 text:

DBF.with_open(
  "customers.dbf",
  [numeric: :exact, encoding: :windows_1252, encoding_errors: :strict],
  fn db -> DBF.get(db, 0) end
)

Resource ownership and errors

Use open/1,2 with close/1 when the database must outlive a callback, such as for suspended enumeration. Every successful open must have a corresponding close. close/1 is idempotent.

Non-bang operations return {:error, %DBF.DatabaseError{}}. open!/1,2 raises that same exception type when opening fails.

Summary

Functions

Closes the DBF and memo resources owned by an open database.

Gets a record by its zero-based physical index.

Opens a DBF file and returns an opaque database value.

Opens a DBF file, raising DBF.DatabaseError on failure.

Opens a database for the duration of a callback and always attempts to close it.

Types

close_result()

@type close_result() :: :ok | error_result()

encoding()

@type encoding() :: :auto | :raw | :windows_1251 | :windows_1252

encoding_error_policy()

@type encoding_error_policy() :: :strict | :replace | :raw

error_reason()

@type error_reason() ::
  :file_not_found
  | :file_error
  | :close_failed
  | :invalid_options
  | :invalid_record_index
  | :unsupported_version
  | :missing_memo_file
  | :unsupported_field_type
  | :invalid_header
  | :invalid_schema
  | :invalid_record
  | :invalid_memo
  | :invalid_encoding

error_result()

@type error_result() :: {:error, DBF.DatabaseError.t()}

numeric_policy()

@type numeric_policy() :: :float | :exact

open_result()

@type open_result() :: {:ok, DBF.Database.t()} | error_result()

option()

@type option() ::
  {:memo_file, String.t() | nil}
  | {:numeric, numeric_policy()}
  | {:encoding, encoding()}
  | {:encoding_errors, encoding_error_policy()}

options()

@type options() :: [option()]

record()

@type record() :: %{optional(String.t()) => term()}

record_result()

@type record_result() :: {record_status(), record()} | error_result()

record_status()

@type record_status() :: :record | :deleted_record

with_open_result(result)

@type with_open_result(result) :: result | error_result()

Functions

close(database)

@spec close(DBF.Database.t()) :: close_result()

Closes the DBF and memo resources owned by an open database.

Closing the same database more than once is safe. After closing, the database must not be used for random access or resumed enumeration.

{:ok, db} = DBF.open("customers.dbf")
:ok = DBF.close(db)
:ok = DBF.close(db)

get(db, record_number)

@spec get(DBF.Database.t(), term()) :: record_result()

Gets a record by its zero-based physical index.

Active and deleted records retain their status. Invalid indexes return an :invalid_record_index database error.

DBF.with_open("customers.dbf", fn db ->
  case DBF.get(db, 2) do
    {:record, row} -> {:ok, row}
    {:deleted_record, row} -> {:deleted, row}
    {:error, error} -> {:error, error}
  end
end)

open(filename, options \\ [])

@spec open(String.t(), options()) :: open_result()

Opens a DBF file and returns an opaque database value.

The caller owns the returned resource and must eventually call close/1. Prefer with_open/2,3 when callback-scoped access is sufficient.

Options are described in the module documentation.

Example

case DBF.open("customers.dbf", numeric: :exact) do
  {:ok, db} ->
    try do
      DBF.get(db, 0)
    after
      DBF.close(db)
    end

  {:error, error} ->
    {:error, Exception.message(error)}
end

open!(filename, options \\ [])

@spec open!(String.t(), options()) :: DBF.Database.t()

Opens a DBF file, raising DBF.DatabaseError on failure.

Like open/2, the caller owns the returned resource and must call close/1. This is useful when failure should abort the current operation:

db = DBF.open!("customers.dbf")

try do
  Enum.take(db, 10)
after
  DBF.close(db)
end

with_open(filename, fun)

@spec with_open(String.t(), (DBF.Database.t() -> result)) :: with_open_result(result)
when result: term()

Opens a database for the duration of a callback and always attempts to close it.

This is the preferred lifecycle for complete reads. The callback result is returned unchanged. If opening or closing fails, an error tuple is returned. If the callback raises, throws, or exits, DBFex closes the resources before propagating the original failure.

The callback receives an enumerable DBF.Database and must not close it.

Examples

Read every physical record:

DBF.with_open("customers.dbf", fn db ->
  Enum.to_list(db)
end)

Pass decoding options and keep only active rows:

DBF.with_open("customers.dbf", [numeric: :exact], fn db ->
  Enum.flat_map(db, fn
    {:record, row} -> [row]
    {:deleted_record, _row} -> []
    {:error, error} -> raise error
  end)
end)

with_open(filename, options, fun)

@spec with_open(String.t(), options(), (DBF.Database.t() -> result)) ::
  with_open_result(result)
when result: term()