flyp-inc/etabase/1.0.0

NOTE: the latest version is 2.0.0

Table

Represents a "table" semantic, where a "table" is a a dictionary of values that have a unique identity, and a "created at" timestamp.

In a traditional relational database management system, the database engine provides a unique identity and can generate timestamps; the Table type takes responsibility for mediating those operations.

Types

type Table a

Opaque type representing a "table".

Create a new Table.

type alias Row a =
{ id : Id a
, value : a
, createdAt : Posix
}

Represents a row created in a Table a.

type Id a

Represents a unique ID for a Row in a Table a.

unwrapId : Id a -> String

Unwrap the inner value of an Id a. This is primarily exposed to facilitate creating a Spec a b, where the b contains a value Id c.

Configuration

type Config a

Configuration details for a table.

Define a table's configuration.

This value returns an empty Config a, and can be "built" with other functions in this module whose type signature ends in Config a -> Config a.

type alias Spec a b =
{ index : Index a
, predicate : Predicate a b
}

A Spec a b is a record that contains:

  • An Index a, which makes lookups faster for a given column
  • A Predicate a b, which allows searching for a Row a in a Table a by some value b, which has the same type as the value indexed
type Index a

Represent an index for a table.

Internally, an index is:

  • A unique identifier (i.e., the index name)
  • A map between a property on an a and an Int

When you query a table, you query it by its indexes. This doesn't mean that you need to create an index for every column! The columns that you should create indexes for are the columns that will help you identify a record, or identify a group of records by some column that is a member of a smaller set compared to other columns.

Consider the following:

module User exposing (..)


import Table


type alias Record =
    { emailAddress : String
    , catchphrase : String
    , role : Role
    }


type Role
    = Admin
    | Member

If we need to run queries against User.Record to find all users that are Admin users, it would make sense to create an index on the .role column, because the Role type is a member of a set that only has two values - Admin or Member.

It wouldn't make much sense to create an index on the .catchphrase column - since that column is likely going to be unique for each user, anyway - so if we really needed to do a query against the .catchphrase column, we would have to consider every record separately, no matter what.

type Predicate a b

An opaque function that acts as an interface between the Index a values within a table's Config a, and the type of a column that has an Index a.

See usage at where_ for more information.

toSpec : String -> (a -> b) -> (b -> String) -> Spec a b

Create an Index a and Predicate a b by providing a unique name for the index, and a string representation of the indexed value.

toSpecBool : String -> (a -> Bool) -> Spec a Bool

Convenience function to create a Spec a b where the b is a Bool.

Add an Index a to a Table a.

Commands

cons : Config a -> Posix -> a -> Table a -> ( Row a, Table a )

Insert a record into a Table.

consBatch : Config a -> Posix -> List a -> Table a -> ( List (Row a), Table a )

Insert many records into a Table.

update : Config a -> Id a -> Posix -> a -> Table a -> Table a

Update a record in a Table.

The Table a type doesn't track "updated at", since it's assumed that if you care about that, you will wrap each column whose "updated at" time is meaningful, in a Col a.

update doesn't return a tuple of the updated record and the updated table; cons only returns a tuple because cons is responsible for creating the Id a value.

update is an expensive operation, because it rebuilds the indexes for a Table a; prefer data structures such that "things that change often" are defined as calls to cons, and "things that only change once in awhile" are handled by calls to update.

delete : Config a -> Posix -> Id a -> Table a -> Table a

Delete a record from a Table.

Queries

getById : Id a -> Table a -> Maybe (Row a)

Get a value by its id.

where_ : Predicate a b -> b -> Table a -> Table a

Query values in a table based on a Predicate a b.

filter : (a -> Bool) -> Table a -> Table a

Query values in a table based on a predicate derived from any column value.

This does not use any of a table's indexes. If you need to use filter, then consider either:

  • Creating an Index a so that you can have a Predicate a b to use with where_, or
  • Filtering the result set down as much as possible by where_ first, before applying filter
select : (Row a -> b) -> Table a -> List b

Select rows from a Table a.

This will operate on all rows in a given Table a, so it may be wise to use where_ and filter to narrow the Table to what you actually want before calling select.

JSON

Note: There is no encoder or decoder for Table a. Serializing and deserializing identity generation would be a tricky thing to have to do, so for now I am making the artistic choice to deal with it by not dealing with it at all.

idEncoder : Id a -> Value

Encoder for an Id a.

idDecoder : Decoder (Id a)

Decoder for an Id a.

rowEncoder : (a -> Value) -> Row a -> Value

Encoder for a Row a.

rowDecoder : Decoder a -> Decoder (Row a)

Decoder for a Row a.