RethinkDb.Driver.FSharp
Functions
Getting Oriented
Open an Extra Namespace
In order to access the functions, you will need to open RethinkDb.Driver.FSharp.Functions
in addition to its parent namespace. This was done to allow RethinkDb.Driver.FSharp
to be open
ed without bringing in all of the function names. As ReQL commands are common words (insert
, update
, get
, etc.), they have a much higher likelihood of name collisions with user code that things like rethink
and UpperBound
.
There's No Overloading in Functions!
Unlike the Domain Specific Language (DSL), plain functions do not support overloading. However, by following patterns, this API is still pretty easy to discover.
[x]Func
is a variant of the function that takes anReqlExpr -> obj
function as its main parameter (ex.updateFunc
)[x]JS
is a variant of the function that takes a string with a server-side executed JavaScript function (ex.updateJS
)[x]WithOptArgs
is a variant of the function that takes (usually) a list of strongly-type optional arguments (ex.insertWithOptArgs
)[x]WithCancel
, on some functions, takes a cancellation token as a parameter
Functions also can support a combination of the above, as well as Async<'T>
and synchronous 'T
versions; asyncWriteResultWithOptArgsAndCancel
is probably close to the longest function name. It's more verbose than the DSL, but for those who view overloading as cognitive overhead, they will likely prefer this version.
(re)Trying Times
Version beta-03
fixed an issue with the withRetry[x]
functions; if you are seeing errors with code that should be working, ensure you are on that version.
Using the Functions
If you read through the concepts page, you have seen some examples; let's look at several scenarios in depth.
Retrieving an Object by Its ID
ReQL will let you start a query by specifying a database (with r.Db()
) or by specifying a table (with r.Table()
); in the latter case, the query will use the default database for the connection. Within the functions, both db
and fromTable
may be starting places. Assuming the default database is “example”, the following two queries are equivalent:
// Type: string -> IConnection -> Task<User option> let findUserById (userId : string) conn = backgroundTask { let! namedDb = db "example" |> table "User" |> get userId |> runResult<User> |> asOption |> withRetryDefault |> withConn conn let! defaultDb = fromTable "User" |> get userId |> runResult<User> |> asOption |> withRetryDefault |> withConn conn // Both namedDb and defaultDb should be pointing to the same object return namedDb }
Notice that runResult
is generic; giving it a type means that the results will have that type. Also, assuming User
is a [<CLIMutable>]
-decorated record, piping it through asOption
will transform a no-find (null
) to an option
.
Note also that unlike the rethink
computation expression, which usually defines an action that can be executed when it is passed a connection, this style performs the action. To accomplish the other behavior, you would have to move the conn parameter:
// Type: string -> (IConnection -> Task<User option>) let findByUserId (userId : string) = fun conn -> backgroundTask { // do stuff {
Either form is acceptable; this is a matter of style preference.
Retrieving a List of Objects
Here is a query to get a list of active users:
// Type: IConnection -> Task<User list> let findActiveUsers conn = backgroundTask { return! fromTable "User" |> getAllWithIndex [ true ] "isActive" |> orderByFunc (fun row -> r.Array (row["lastName"], row["firstName"])) |> runResult<User list> |> withRetryDefault |> withConn conn }
Here, we use the version of getAll
that specifies an index name, and the version of orderBy
that uses a ReQL function.
Inserting Data
Objects can be inserted one at a time (insert
) or in multiples (insertMany
). Inserts also have an “on conflict” optional argument that specifies what to do if a document with the given ID already exists in the table. The below example attempts to insert a user, specifying to update the existing record if it already exists; it also will throw an exception if there is an error.
// Type: User -> IConnection -> Task<unit> let addUser (user : User) conn = backgroundTask { let! _ = fromTable "User" |> insertWithOptArgs user [ OnConflict Update ] |> runWrite |> withRetryDefault |> withConn conn }
Updating Data
In ReQL, update can be called on a single record or a collection of records, including an entire table. The concepts page showed a complex update-with-function query; we'll go much more basic here, updating our user's first and last name. We will also return true
if we updated anything, or false
if not.
// Type: User -> IConnection -> Task<bool> let updateUser (user : User) conn = backgroundTask { let! result = fromTable "User" |> get user.id |> update {| lastName = user.lastName; firstName = user.firstName |} // or // update (r.HashMap("lastName", user.lastName).With ("firstName, user.firstName)) |> runWrite |> withRetryDefault |> withConn conn } return result.Replaced > 0UL }
Deleting Data
Like update
, delete
can empty a table; be sure to get
, getAll
, filter
, or however you need to identify the documents to be deleted before calling delete. For our example, we'll delete all inactive users, and return how many users we deleted.
// Type: IConnection -> Task<int> let deleteInactiveUsers conn = backgroundTask { let! result = fromTable "User" |> getAllWithIndex [ false ] "isActive" |> delete |> runWrite |> withRetryDefault |> withConn conn } return int result.Deleted }
Other Operations
This same pattern works for manipulating databases, tables, and indexes. dbList
, tableList
, and indexList
all return string list
, and the [x]create
and [x]drop
commands work with runWrite
, Result
s, and the like. For operations that start with a table, the functions that do not require db
to be called end with [from/in]Default
(ex. tableCreate
's signature is string -> Db -> TableCreate
, while tableCreateInDefault
's signature is string -> TableCreate
).
You can view the entire list of functions to see what is available; and, if you see something missing, open an issue!