diff --git a/LICENSE b/LICENSE index 8ddfce5..d000069 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,43 @@ -MIT License +This is a modified Apache License: + +You are permitted to use, compile, execute, the code in this repository, +as long as this driver's protocol output is not sent over the SSL/TLS +protocol. You are prohibited from connecting this driver to Compose.IO +or to any other SSL/TLS endpoint without a commercial license. + +To remove this limitation and permit usage over SSL/TLS, a commercial +license must be purchased. The commercial license permitting use over +SSL/TLS and Compose.IO is available from Bit Armory Inc and can +be purchased here: + + https://www.bitarmory.com/payments/rethinkdb + +Copyright 2010-2012 RethinkDB + +Copyright (c) .NET Foundation. All rights reserved. + +Copyright (c) Daniel Cannon. + +Copyright (c) 2015 Bitly. + +Copyright 2010-2014 MongoDB Inc. + +Copyright (c) 2016 Bit Armory Inc. + +Copyright (c) 2016 Brian Chavez +* http://github.com/bchavez +* http://bchavez.bitarmory.com Copyright (c) 2017 Daniel J. Summers +* http://github.com/danieljsummers -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +these files except in compliance with the License. You may obtain a copy of the +License at -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +http://www.apache.org/licenses/LICENSE-2.0 -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index d38726f..f0a8ac0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,69 @@ # RethinkDb.Driver.FSharp Idiomatic F# extensions for the C# RethinkDB driver + +## Licensing + +While no specific additional license restrictions exist for this project, there are modifications to the Apache v2 +license on this project's dependencies. Please see [the heading on the C# driver page][license] for details. + +## Using + +It is still early days on this project; however, AppVeyor CI provides a [NuGet feed][nuget] that builds packages for +each commit. + +## Goals + +The goal is to provide: +- A composable pipeline for creating ReQL statements: + +```fsharp +/// string -> (IConnection -> Async) +let fetchPost (postId : string) = + fromDb "Blog" + |> table "Post" + |> get postId + |> asyncResult +``` + +- An F# domain-specific language (DSL) using a `rethink` computation expression: + +```fsharp +/// string -> (IConnection -> Async) +let fetchPost (postId : string) = + rethink { + fromDb "Blog" + table "Post" + get postId + asyncResult + } +``` + +- A standard way to translate JSON into a strongly-typed configuration: + +```fsharp +/// type: DataConfig +let config = DataConfig.fromJsonFile "data-config.json" + +/// type: IConnection +let conn = config.Connect () + +/// type: Post (utilizing either example above) +let post = fetchPost "the-post-id" conn |> Async.RunSynchronously +``` + +- Only rename functions/methods where required + +```fsharp +// Function names cannot be polymorphic the way object-oriented methods can, so filter's three overloads become +filter r.HashMap("age", 30) +// and +filterFunc (fun row -> row.["age"].Eq(30)) +// and +filterJS "function (row) { return 30 == row['age'] }" +``` + +The composable pipeline and the JSON configuration are the early goals, as the computation expression will utilize the +same composition as those functions. + +[license]: https://github.com/bchavez/RethinkDb.Driver#open-source-and-commercial-licensing +[nuget]: https://ci.appveyor.com/nuget/danieljsummers-rethinkdb-driver-fsharp \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..9e0196d --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,11 @@ +os: Visual Studio 2017 + +build_script: + - cmd: dotnet restore src\RethinkDb.Driver.FSharp.fsproj + - cmd: dotnet build -v n src\RethinkDb.Driver.FSharp.fsproj + - cmd: dotnet pack src\RethinkDb.Driver.FSharp.fsproj -o %CD%\artifacts\nupkg + +artifacts: + - path: artifacts\nupkg\*.nupkg + +tests: off diff --git a/src/RethinkDb.Driver.FSharp/Config.fs b/src/RethinkDb.Driver.FSharp/Config.fs new file mode 100644 index 0000000..4280ef2 --- /dev/null +++ b/src/RethinkDb.Driver.FSharp/Config.fs @@ -0,0 +1,95 @@ +namespace RethinkDb.Driver.FSharp + +open Newtonsoft.Json.Linq +open RethinkDb.Driver +open RethinkDb.Driver.Net + +/// 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 + +/// 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 diff --git a/src/RethinkDb.Driver.FSharp/Functions.fs b/src/RethinkDb.Driver.FSharp/Functions.fs new file mode 100644 index 0000000..0685b3b --- /dev/null +++ b/src/RethinkDb.Driver.FSharp/Functions.fs @@ -0,0 +1,80 @@ +[] +module RethinkDb.Driver.FSharp.Functions + +open RethinkDb.Driver + +let private r = RethinkDB.R + +/// Get a connection builder that can be used to create one RethinkDB connection +let connection () = + r.Connection () + +/// Get the results of an expression +let asyncResult<'T> conn (expr : Ast.ReqlExpr) = + expr.RunResultAsync<'T> conn + |> Async.AwaitTask + +/// Get the result of a non-select ReQL expression +let asyncReqlResult conn (expr : Ast.ReqlExpr) = + expr.RunResultAsync conn + |> Async.AwaitTask + +/// Get a list of databases +let dbList conn = + r.DbList () + |> asyncResult conn + +/// Create a database +let dbCreate dbName conn = + r.DbCreate dbName + |> asyncReqlResult conn + +/// Reference a database +let db dbName = + r.Db dbName + +/// Reference the default database +let defaultDb = + (fun () -> r.Db ()) () + +/// Get a list of tables for the given database +let tableList conn (db : Ast.Db) = + db.TableList () + |> asyncResult conn + +/// Create a table in the given database +let tableCreate tableName conn (db : Ast.Db) = + db.TableCreate tableName + |> asyncReqlResult conn + +/// Return all documents in a table (may be further refined) +let table tableName (db : Ast.Db) = + db.Table tableName + +/// Return all documents in a table from the default database (may be further refined) +let fromTable tableName = + table tableName defaultDb + +/// Get a list of indexes for the given table +let indexList conn (table : Ast.Table) = + table.IndexList () + |> asyncResult conn + +/// Create an index on the given table +let indexCreate indexName conn (table : Ast.Table) = + table.IndexCreate indexName + |> asyncReqlResult conn + +/// Get a document by its primary key +let get documentId (table : Ast.Table) = + table.Get documentId + +/// Get all documents matching keys in the given index +let getAll (ids : 'T seq) indexName (table : Ast.Table) = + table.GetAll(ids |> Array.ofSeq).OptArg("index", indexName) + + +/// Get a cursor with the results of an expression +let asyncCursor<'T> conn (expr : Ast.ReqlExpr) = + expr.RunCursorAsync<'T> conn + |> Async.AwaitTask \ No newline at end of file diff --git a/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj b/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj new file mode 100644 index 0000000..9551936 --- /dev/null +++ b/src/RethinkDb.Driver.FSharp/RethinkDb.Driver.FSharp.fsproj @@ -0,0 +1,29 @@ + + + + net45;netstandard1.6 + Idiomatic F# extentions to the official RethinkDB C# driver + Daniel J. Summers + https://github.com/danieljsummers/RethinkDb.Driver.FSharp/blob/master/LICENSE + https://github.com/danieljsummers/RethinkDb.Driver.FSharp + + false + Alpha; use at your own risk + See LICENSE + RethinkDB document F# + 0.7.0 + alpha-0001 + + + + + + + + + + + + + +