diff --git a/src/RethinkDb.Driver.FSharp.sln b/src/RethinkDb.Driver.FSharp.sln index 402be4c..691e7de 100644 --- a/src/RethinkDb.Driver.FSharp.sln +++ b/src/RethinkDb.Driver.FSharp.sln @@ -1,9 +1,8 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{E3D587AC-BEA4-4336-919D-1B6F1397C8FD}") = "RethinkDb.Driver.FSharp", "RethinkDb.Driver.FSharp\RethinkDb.Driver.FSharp.fsproj", "{9026E009-55DC-454E-87B7-39B9CBAD8BF8}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "RethinkDb.Driver.FSharp", "RethinkDb.Driver.FSharp\RethinkDb.Driver.FSharp.fsproj", "{9026E009-55DC-454E-87B7-39B9CBAD8BF8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -14,19 +13,24 @@ Global Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Debug|x64.ActiveCfg = Debug|x64 - {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Debug|x64.Build.0 = Debug|x64 - {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Debug|x86.ActiveCfg = Debug|x86 - {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Debug|x86.Build.0 = Debug|x86 + {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Debug|x64.ActiveCfg = Debug|Any CPU + {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Debug|x64.Build.0 = Debug|Any CPU + {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Debug|x86.ActiveCfg = Debug|Any CPU + {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Debug|x86.Build.0 = Debug|Any CPU {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Release|Any CPU.Build.0 = Release|Any CPU - {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Release|x64.ActiveCfg = Release|x64 - {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Release|x64.Build.0 = Release|x64 - {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Release|x86.ActiveCfg = Release|x86 - {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Release|x86.Build.0 = Release|x86 + {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Release|x64.ActiveCfg = Release|Any CPU + {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Release|x64.Build.0 = Release|Any CPU + {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Release|x86.ActiveCfg = Release|Any CPU + {9026E009-55DC-454E-87B7-39B9CBAD8BF8}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F557ECA4-998C-4FBE-9B59-ABFA3F8A84DF} + EndGlobalSection +EndGlobal diff --git a/src/RethinkDb.Driver.FSharp/Builder.fs b/src/RethinkDb.Driver.FSharp/Builder.fs new file mode 100644 index 0000000..1776514 --- /dev/null +++ b/src/RethinkDb.Driver.FSharp/Builder.fs @@ -0,0 +1,189 @@ +[] +module RethinkDb.Driver.FSharp.RethinkBuilder + +open RethinkDb.Driver +open RethinkDb.Driver.Ast +open RethinkDb.Driver.Net +open System.Threading.Tasks + +/// Computation Expression builder for RethinkDB queries +type RethinkBuilder<'T> () = + + /// Create a RethinkDB hash map of the given field/value pairs + let fieldsToMap (fields : (string * obj) list) = + fields + |> List.fold (fun (m : Model.MapObject) item -> m.With (fst item, snd item)) (RethinkDB.R.HashMap ()) + + member _.Bind (expr : ReqlExpr, f : ReqlExpr -> ReqlExpr) = f expr + + member this.For (expr, f) = this.Bind (expr, f) + + member _.Yield _ = RethinkDB.R + + // meta queries (tables, indexes, etc.) + + /// List all databases + [] + member _.DbList (r : RethinkDB) = r.DbList () + + /// Create a database + [] + member _.DbCreate (r : RethinkDB, db : string) = r.DbCreate db + + /// List all tables for the default database + [] + member _.TableList (r : RethinkDB) = r.TableList () + + /// List all tables for the specified database + [] + member _.TableListWithDb (r : RethinkDB, db : string) = r.Db(db).TableList () + + /// Create a table in the default database + [] + member _.TableCreate (r : RethinkDB, table : string) = r.TableCreate table + + /// Create a table in the default database + [] + member _.TableCreateWithDb (r : RethinkDB, table : string, db : string) = r.Db(db).TableCreate table + + /// List all indexes for a table + [] + member _.IndexList (tbl : Table) = tbl.IndexList () + + /// Create an index for a table + [] + member _.IndexCreate (tbl : Table, index : string) = tbl.IndexCreate index + + /// Create an index for a table, using a function to calculate the index + [] + member _.IndexCreate (tbl : Table, index : string, f : ReqlExpr -> obj) = tbl.IndexCreate (index, ReqlFunction1 f) + + // database/table identification + + /// Specify a database for further commands + [] + member _.Db (expr : RethinkDB, db : string) = expr.Db db + + /// Specify a table in the default database + [] + member _.TableInDefaultDb (expr : RethinkDB, table : string) = expr.Table table + + /// Specify a table in a specific database + [] + member _.Table (expr : RethinkDB, table : string, db : string) = expr.Db(db).Table table + + /// Create an equality join with another table + [] + member _.EqJoin (expr : ReqlExpr, field : string, otherTable : string) = + expr.EqJoin (field, RethinkDB.R.Table otherTable) + + // data retrieval / manipulation + + /// Get a document from a table by its ID + [] + member _.Get (tbl : Table, key : obj) = tbl.Get key + + /// Get all documents matching the given index value + [] + member _.GetAll (tbl : Table, keys : obj list, index : string) = + tbl.GetAll(Array.ofList keys).OptArg ("index", index) + + /// Limit the results of this query + [] + member _.Limit (expr : ReqlExpr, limit : int) = expr.Limit limit + + /// Count documents for the current query + [] + member _.Count (expr : ReqlExpr) = expr.Count () + + /// Filter a query by a single field value + [] + member _.Filter (expr : ReqlExpr, field : string, value : obj) = expr.Filter (fieldsToMap [ field, value ]) + + /// Filter a query by multiple field values + [] + member _.Filter (expr : ReqlExpr, filter : (string * obj) list) = expr.Filter (fieldsToMap filter) + + /// Filter a query by a function + [] + member _.Filter (expr : ReqlExpr, f : ReqlExpr -> obj) = expr.Filter (ReqlFunction1 f) + + /// Filter a query by multiple functions (has the effect of ANDing them) + [] + member _.Filter (expr : ReqlExpr, fs : (ReqlExpr -> obj) list) = + fs |> List.fold (fun (e : ReqlExpr) f -> e.Filter (ReqlFunction1 f)) expr + + /// Map fields for the current query + [] + member _.Map (expr : ReqlExpr, f : ReqlExpr -> obj) = expr.Map (ReqlFunction1 f) + + /// Exclude the given fields from the output + [] + member _.Without (expr : ReqlExpr, fields : obj list) = expr.Without (Array.ofList fields) + + /// Combine a left and right selection into a single record + [] + member _.Zip (expr : ReqlExpr) = expr.Zip () + + /// Merge a document into the current query + [] + member _.Merge (expr : ReqlExpr, f : ReqlExpr -> obj) = expr.Merge (ReqlFunction1 f) + + /// Pluck (select only) specific fields from the query + [] + member _.Pluck (expr : ReqlExpr, fields : string list) = expr.Pluck (Array.ofList fields) + + /// Order the results by the given field value (ascending) + [] + member _.OrderBy (expr : ReqlExpr, field : string) = expr.OrderBy field + + /// Order the results by the given function value + [] + member _.OrderBy (expr : ReqlExpr, f : ReqlExpr -> obj) = expr.OrderBy (ReqlFunction1 f) + + /// Order the results by the given field value (descending) + [] + member _.OrderByDescending (expr : ReqlExpr, field : string) = expr.OrderBy (RethinkDB.R.Desc field) + + /// Insert a document into the given table + [] + member _.Insert (tbl : Table, doc : obj) = tbl.Insert doc + + /// Update specific fields in a document + [] + member _.Update (expr : ReqlExpr, fields : (string * obj) list) = expr.Update (fieldsToMap fields) + + /// Replace the current query with the specified document + [] + member _.Replace (expr : ReqlExpr, doc : obj) = expr.Replace doc + + /// Delete the document(s) identified by the current query + [] + member _.Delete (expr : ReqlExpr) = expr.Delete () + + // executing queries + + /// Execute the query, returning the result of the type specified + [] + member _.Result (expr : ReqlExpr) : IConnection -> Task<'T> = + fun conn -> task { + return! expr.RunResultAsync<'T> conn + } + + /// Execute the query, returning the result of the type specified, or None if no result is found + [] + member _.ResultOption (expr : ReqlExpr) : IConnection -> Task<'T option> = + fun conn -> task { + let! result = expr.RunResultAsync<'T> conn + return match (box >> isNull) result with true -> None | false -> Some result + } + + /// Perform a write operation + [] + member _.Write (expr : ReqlExpr) : IConnection -> Task = + fun conn -> task { + return! expr.RunWriteAsync conn + } + +/// RethinkDB computation expression +let rethink<'T> = RethinkBuilder<'T> () diff --git a/src/RethinkDb.Driver.FSharp/Config.fs b/src/RethinkDb.Driver.FSharp/Config.fs index 4280ef2..50bec96 100644 --- a/src/RethinkDb.Driver.FSharp/Config.fs +++ b/src/RethinkDb.Driver.FSharp/Config.fs @@ -1,95 +1,118 @@ namespace RethinkDb.Driver.FSharp +open Microsoft.Extensions.Configuration open Newtonsoft.Json.Linq open RethinkDb.Driver open RethinkDb.Driver.Net +open System.Threading.Tasks /// Parameters for the RethinkDB configuration type DataConfigParameter = - | Hostname of string - | Port of int - | User of string * string - | AuthKey of string - | Timeout of int - | Database of string + | Hostname of string + | Port of int + | User of string * string + | AuthKey of string + | Timeout of int + | Database of string + + +/// Connection builder function +module private ConnectionBuilder = + let build (builder : Connection.Builder) block = + match block with + | Hostname x -> builder.Hostname x + | Port x -> builder.Port x + | User (x, y) -> builder.User (x, y) + | AuthKey x -> builder.AuthKey x + | Timeout x -> builder.Timeout x + | Database x -> builder.Db x + /// RethinDB configuration type DataConfig = - { Parameters : DataConfigParameter list } -with - static member empty = - { Parameters = [] } - /// Create a RethinkDB connection - member this.CreateConnection () : IConnection = - let folder (builder : Connection.Builder) block = - match block with - | Hostname x -> builder.Hostname x - | Port x -> builder.Port x - | User (x, y) -> builder.User (x, y) - | AuthKey x -> builder.AuthKey x - | Timeout x -> builder.Timeout x - | Database x -> builder.Db x - let bldr = - this.Parameters - |> Seq.fold folder (RethinkDB.R.Connection ()) - upcast bldr.Connect () - /// The effective hostname - member this.Hostname = - match this.Parameters - |> List.tryPick (fun x -> match x with Hostname _ -> Some x | _ -> None) with - | Some (Hostname x) -> x - | _ -> RethinkDBConstants.DefaultHostname - /// The effective port - member this.Port = - match this.Parameters - |> List.tryPick (fun x -> match x with Port _ -> Some x | _ -> None) with - | Some (Port x) -> x - | _ -> RethinkDBConstants.DefaultPort - /// The effective connection timeout - member this.Timeout = - match this.Parameters - |> List.tryPick (fun x -> match x with Timeout _ -> Some x | _ -> None) with - | Some (Timeout x) -> x - | _ -> RethinkDBConstants.DefaultTimeout - /// The effective database - member this.Database = - match this.Parameters - |> List.tryPick (fun x -> match x with Database _ -> Some x | _ -> None) with - | Some (Database x) -> x - | _ -> RethinkDBConstants.DefaultDbName - /// Parse settings from JSON - /// - /// A sample JSON object with all the possible properties filled in: - /// { - /// "hostname" : "my-host", - /// "port" : 12345, - /// "username" : "my-user-name", - /// "password" : "my-password", - /// "auth-key" : "my-auth-key", - /// "timeout" : 77, - /// "database" : "default-db" - /// } - /// - /// None of these properties are required, and properties not matching any of the above listed ones will be ignored. - static member FromJson json = - let isNotNull = not << isNull - let parsed = JObject.Parse json - let config = - seq { - match parsed.["hostname"] with x when isNotNull x -> yield Hostname <| x.Value () | _ -> () - match parsed.["port"] with x when isNotNull x -> yield Port <| x.Value () | _ -> () - match parsed.["auth-key"] with x when isNotNull x -> yield AuthKey <| x.Value () | _ -> () - match parsed.["timeout"] with x when isNotNull x -> yield Timeout <| x.Value () | _ -> () - match parsed.["database"] with x when isNotNull x -> yield Database <| x.Value () | _ -> () - let userName = parsed.["username"] - let password = parsed.["password"] - match isNotNull userName && isNotNull password with - | true -> yield User (userName.Value (), password.Value ()) - | _ -> () - } - |> List.ofSeq - { Parameters = config } - /// Parse settings from a JSON text file - /// - /// See doc for FromJson for the expected JSON format. - static member FromJsonFile = System.IO.File.ReadAllText >> DataConfig.FromJson \ No newline at end of file + { Parameters : DataConfigParameter list } + + /// An empty configuration + static member empty = + { Parameters = [] } + + /// Create a RethinkDB connection + member this.CreateConnection () : IConnection = + this.Parameters + |> Seq.fold ConnectionBuilder.build (RethinkDB.R.Connection ()) + |> function builder -> builder.Connect () + + /// Create a RethinkDB connection + member this.CreateConnectionAsync () : Task = + this.Parameters + |> Seq.fold ConnectionBuilder.build (RethinkDB.R.Connection ()) + |> function builder -> builder.ConnectAsync () + + /// The effective hostname + member this.Hostname = + match this.Parameters |> List.tryPick (fun x -> match x with Hostname _ -> Some x | _ -> None) with + | Some (Hostname x) -> x + | _ -> RethinkDBConstants.DefaultHostname + + /// The effective port + member this.Port = + match this.Parameters |> List.tryPick (fun x -> match x with Port _ -> Some x | _ -> None) with + | Some (Port x) -> x + | _ -> RethinkDBConstants.DefaultPort + + /// The effective connection timeout + member this.Timeout = + match this.Parameters |> List.tryPick (fun x -> match x with Timeout _ -> Some x | _ -> None) with + | Some (Timeout x) -> x + | _ -> RethinkDBConstants.DefaultTimeout + + /// The effective database + member this.Database = + match this.Parameters |> List.tryPick (fun x -> match x with Database _ -> Some x | _ -> None) with + | Some (Database x) -> x + | _ -> RethinkDBConstants.DefaultDbName + + /// Parse settings from JSON + /// + /// A sample JSON object with all the possible properties filled in: + /// { + /// "hostname" : "my-host", + /// "port" : 12345, + /// "username" : "my-user-name", + /// "password" : "my-password", + /// "auth-key" : "my-auth-key", + /// "timeout" : 77, + /// "database" : "default-db" + /// } + /// + /// None of these properties are required, and properties not matching any of the above listed ones will be ignored. + static member FromJson json = + let parsed = JObject.Parse json + { Parameters = + [ match parsed["hostname"] with null -> () | x -> Hostname <| x.Value () + match parsed["port"] with null -> () | x -> Port <| x.Value () + match parsed["auth-key"] with null -> () | x -> AuthKey <| x.Value () + match parsed["timeout"] with null -> () | x -> Timeout <| x.Value () + match parsed["database"] with null -> () | x -> Database <| x.Value () + match parsed["username"], parsed["password"] with + | null, _ | _, null -> () + | userName, password -> User (userName.Value (), password.Value ()) + ] + } + + /// Parse settings from a JSON text file + /// + /// See doc for FromJson for the expected JSON format. + static member FromJsonFile = System.IO.File.ReadAllText >> DataConfig.FromJson + + /// Parse settings from application configuration + static member FromConfiguration (cfg : IConfigurationSection) = + { Parameters = + [ match cfg["hostname"] with null -> () | host -> Hostname host + match cfg["port"] with null -> () | port -> Port (int port) + match cfg["auth-key"] with null -> () | key -> AuthKey key + match cfg["timeout"] with null -> () | time -> Timeout (int time) + match cfg["database"] with null -> () | db -> Database db + match cfg["username"], cfg["password"] with null, _ | _, null -> () | user -> User user + ] + } diff --git a/src/RethinkDb.Driver.FSharp/Functions.fs b/src/RethinkDb.Driver.FSharp/Functions.fs index 3bd0eb1..df7f390 100644 --- a/src/RethinkDb.Driver.FSharp/Functions.fs +++ b/src/RethinkDb.Driver.FSharp/Functions.fs @@ -14,7 +14,7 @@ let asyncCursor<'T> conn (expr : ReqlExpr) = /// Get the result of a non-select ReQL expression let asyncReqlResult conn (expr : ReqlExpr) = - expr.RunResultAsync conn + expr.RunWriteAsync conn |> Async.AwaitTask /// Get the results of an expression diff --git a/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj b/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj index dbf05fa..ddd7a2f 100644 --- a/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj +++ b/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj @@ -1,7 +1,7 @@ - + - net45;netstandard1.6 + net6.0 Idiomatic F# extentions to the official RethinkDB C# driver Daniel J. Summers https://github.com/danieljsummers/RethinkDb.Driver.FSharp/blob/master/LICENSE @@ -11,19 +11,19 @@ Alpha; use at your own risk See LICENSE RethinkDB document F# - 0.7.0 - alpha-0004 + 0.7.1 + alpha-0001 + - - - + +