├── .gitignore ├── README.md ├── build.sbt ├── project ├── ApiDefinitions.scala ├── ApiDsl.scala ├── ApiGenerator.scala ├── build.properties └── plugins.sbt ├── scala-reql-akka └── src │ └── main │ └── scala │ └── reql │ └── akka │ ├── ReqlActor.scala │ └── RethinkDbConnectionActor.scala ├── scala-reql-core └── src │ └── main │ └── scala │ └── reql │ ├── dsl │ ├── Cursor.scala │ ├── NestedReqlContext.scala │ ├── ReqlArg.scala │ ├── ReqlArrayDsl.scala │ ├── ReqlContext.scala │ ├── ReqlDocumentDsl.scala │ ├── ReqlEntryPoint.scala │ ├── ReqlQueryException.scala │ ├── ReqlTypesConversions.scala │ ├── TableSpecialOps.scala │ └── types.scala │ └── protocol │ ├── ReqlProtocol.scala │ ├── ReqlQueryType.scala │ ├── ReqlResponseType.scala │ ├── ReqlTcpConnection.scala │ └── ReqlVersion.scala └── scala-reql-pushka └── src └── main └── scala └── reql └── pushka └── PushkaReqlContext.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea* 3 | target/ 4 | .cache 5 | .classpath 6 | .project 7 | .settings/ 8 | *.iml 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Scala RethinkDB Driver 2 | 3 | [![Join the chat at https://gitter.im/fomkin/scala-reql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/fomkin/scala-reql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | The idea is to create pure and simple RethinkDB API implementation which 6 | can be run inside different Scala environments. It means that protocol 7 | implementation and query eDSL are not depend on thirdparty libraries. 8 | It may allow to use library inside Akka, Scalaz and Scala.js based projects. 9 | 10 | Implementation details 11 | 12 | * Functions support 13 | * Cursors support 14 | * Full asynchronous 15 | * Zero-dependency core 16 | * Type safe API is not planned 17 | 18 | Not implemented 19 | 20 | * Part of API 21 | * Pathspecs 22 | 23 | ### Akka and Pushka example 24 | 25 | Add dependencies to your project. Note that you 26 | cant download `scala-reql-akka` from Maven Central. You 27 | should clone this repository and make `sbt publish-local` 28 | first. 29 | 30 | ```scala 31 | libraryDependencies ++= Seq( 32 | "com.github.fomkin" %% "scala-reql-akka" % "0.1.0-SNAPSHOT", 33 | "com.github.fomkin" %% "pushka-json" % "0.2.0", 34 | ) 35 | ``` 36 | 37 | Create actor system and connection to RethinkDB 38 | 39 | ```scala 40 | class Queries(val dbConnection: ActorRef) 41 | extends ReqlActor[Ast] with PushkaReqlContext { 42 | 43 | // Timeout for queries execution 44 | val queryTimeout: Timeout = 2 seconds 45 | 46 | r.db("test").table("animals").get("cow").runA { 47 | case Right(cow) => // work with cow 48 | case Left(error) => // process error 49 | } 50 | } 51 | 52 | val system = new ActorSystem("rethinkdb-client") 53 | val dbConnection = system.actorOf(Props[ReqlTcpConnection]) 54 | ``` 55 | 56 | There are three different ways to run query 57 | 58 | Run atomic. Do not scare modify actors state 59 | inside lambda. It runs in same thread as 60 | receive. 61 | 62 | ```scala 63 | r.db("test").table("animals").get("cow").runA { 64 | case Right(cow) => // work with cow 65 | case Left(error) => // process error 66 | } 67 | ``` 68 | 69 | Run cursor. Like atomic cursors lambda runs 70 | in same thread as receive. 71 | 72 | ```scala 73 | r.db("test").table("animals").changes().runC { cursor => 74 | cursor.foreach { animal => 75 | // Work with update 76 | } 77 | } 78 | ``` 79 | 80 | Run atomic and get future. 81 | 82 | ```scala 83 | r.db("test").table("animals").get("cow").runA pipeTo sender() 84 | ``` 85 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | val commonSettings = Seq( 2 | organization := "com.github.fomkin", 3 | version := "0.4.4", 4 | scalaVersion := "2.11.8", 5 | scalacOptions ++= Seq( 6 | "-deprecation", 7 | "-feature", 8 | "-Xfatal-warnings", 9 | "-language:postfixOps", 10 | "-language:implicitConversions" 11 | ), 12 | publishTo := { 13 | isSnapshot.value match { 14 | case true => Some("iDecide Snapshots" at "https://nexus.flexis.ru/content/repositories/snapshots") 15 | case false => Some("iDecide Releases" at "https://nexus.flexis.ru/content/repositories/releases") 16 | } 17 | } 18 | ) 19 | 20 | val `scala-reql-core` = crossProject.crossType(CrossType.Pure). 21 | settings(commonSettings:_*). 22 | settings(sourceGenerators in Compile <+= sourceManaged in Compile map ApiGenerator) 23 | 24 | lazy val `scala-reql-core-js` = `scala-reql-core`.js 25 | lazy val `scala-reql-core-jvm` = `scala-reql-core`.jvm 26 | 27 | val `scala-reql-pushka` = crossProject.crossType(CrossType.Pure). 28 | dependsOn(`scala-reql-core`). 29 | settings(commonSettings:_*). 30 | settings( 31 | addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full), 32 | libraryDependencies += "com.github.fomkin" %%% "pushka-json" % "0.7.1" 33 | ) 34 | 35 | lazy val `scala-reql-pushka-js` = `scala-reql-pushka`.js 36 | lazy val `scala-reql-pushka-jvm` = `scala-reql-pushka`.jvm 37 | 38 | lazy val `scala-reql-akka` = project. 39 | dependsOn(`scala-reql-core-jvm`). 40 | settings(commonSettings:_*). 41 | settings( 42 | libraryDependencies ++= Seq( 43 | "com.typesafe.akka" %% "akka-actor" % "2.4.9" 44 | ) 45 | ) 46 | 47 | publishTo := Some(Resolver.file("Unused transient repository", file("target/unusedrepo"))) 48 | 49 | publishArtifact := false 50 | -------------------------------------------------------------------------------- /project/ApiDefinitions.scala: -------------------------------------------------------------------------------- 1 | // https://raw.githubusercontent.com/rethinkdb/rethinkdb/v2.1.1/src/rdb_protocol/ql2.proto 2 | object ApiDefinitions { 3 | 4 | def genGroupModule(termType: Int, name: String) = { 5 | module(termType = termType, name = name)(Top.Function, Top.Datum.Obj)( 6 | fun(Top.Sequence)(multiarg("field", Top.Datum.Str)), 7 | fun(name + "_f", Top.Sequence)(multiarg("f", Top.FunctionArg(1))) 8 | ) 9 | } 10 | 11 | val modules = Seq( 12 | 13 | module(termType = 42, name = "distinct")(Top.Sequence)( 14 | fun(Top.Sequence)() 15 | ), 16 | 17 | module(termType = 12, name = "error")(Top.Error)( 18 | fun(arg("message", Top.Datum.Str)) 19 | ), 20 | module(termType = 169, name = "uuid")(Top.Datum)(fun()), 21 | module(termType = 13, name = "implicitVar")(Top.Datum)(fun()), 22 | // TODO hadcode it 23 | module(termType = 153, name = "http")(Top.Datum.Obj, Top.Datum.Str)( 24 | fun(arg("url", Top.Datum.Str)) 25 | ), 26 | 27 | // Takes an HTTP URL and gets it. If the get succeeds and 28 | // returns valid JSON, it is converted into a DATUM 29 | // HTTP = 153; // STRING {data: OBJECT | STRING, 30 | // timeout: !NUMBER, 31 | // method: STRING, 32 | // params: OBJECT, 33 | // header: OBJECT | ARRAY, 34 | // attempts: NUMBER, 35 | // redirects: NUMBER, 36 | // verify: BOOL, 37 | // page: FUNC | STRING, 38 | // page_limit: NUMBER, 39 | // auth: OBJECT, 40 | // result_format: STRING, 41 | // } -> STRING | STREAM 42 | 43 | //------------------------------------------------------------------------- 44 | // 45 | // Math 46 | // 47 | //------------------------------------------------------------------------- 48 | 49 | // EQ = 17; // DATUM... -> BOOL 50 | module(termType = 17, name = "eq")(Top.Datum.Bool)( 51 | fun(arg("a", Top.Datum), arg("b", Top.Datum)), 52 | fun(Top.Datum)(arg("and", Top.Datum)), 53 | fun("===", Top.Datum)(arg("and", Top.Datum)) 54 | ), 55 | // NE = 18; // DATUM... -> BOOL 56 | module(termType = 18, name = "ne")(Top.Datum.Bool)( 57 | fun(arg("a", Top.Datum), arg("b", Top.Datum)), 58 | fun(Top.Datum)(arg("and", Top.Datum)), 59 | fun("!===", Top.Datum)(arg("and", Top.Datum)) 60 | ), 61 | 62 | //LT = 19; // DATUM... -> BOOL 63 | module(termType = 19, name = "lt")(Top.Datum.Bool)( 64 | fun(arg("a", Top.Datum), arg("b", Top.Datum)), 65 | fun(Top.Datum)(arg("thn", Top.Datum)), 66 | fun("<", Top.Datum)(arg("thn", Top.Datum)) 67 | ), 68 | 69 | //GT = 21; // DATUM... -> BOOL 70 | module(termType = 21, name = "gt")(Top.Datum.Bool)( 71 | fun(arg("a", Top.Datum), arg("b", Top.Datum)), 72 | fun(Top.Datum)(arg("thn", Top.Datum)), 73 | fun(">", Top.Datum)(arg("thn", Top.Datum)) 74 | ), 75 | 76 | // NOT = 23; // BOOL -> BOOL 77 | module(termType = 23, name = "not")(Top.Datum.Bool)( 78 | fun(arg("a", Top.Datum.Bool)) 79 | ), 80 | 81 | // Executes its first argument, and returns its second argument if it 82 | // got [true] or its third argument if it got [false] (like an `if` 83 | // statement). 84 | //BRANCH = 65; // BOOL, Top, Top -> Top 85 | module(termType = 65, name = "branch")(Top.AnyType)( 86 | fun(arg("condition", Top.Datum.Bool), arg("thn", Top), arg("els", Top)) 87 | ), 88 | 89 | //OR = 66; // BOOL... -> BOOL 90 | module(termType = 66, name = "or")(Top.Datum.Bool)( 91 | fun(arg("a", Top.Datum.Bool), arg("b", Top.Datum.Bool)), 92 | fun(Top.Datum.Bool)(arg("b", Top.Datum.Bool)), 93 | fun("||", Top.Datum)(arg("b", Top.Datum)) 94 | ), 95 | 96 | // AND = 67; // BOOL... -> BOOL 97 | module(termType = 67, name = "and")(Top.Datum.Bool)( 98 | fun(arg("a", Top.Datum.Bool), arg("b", Top.Datum.Bool)), 99 | fun(Top.Datum.Bool)(arg("b", Top.Datum.Bool)), 100 | fun("&&", Top.Datum)(arg("b", Top.Datum)) 101 | ), 102 | 103 | // SUB = 25; // NUMBER... -> NUMBER 104 | module(termType = 24, name = "add")(Top.Datum.Num)( 105 | fun(multiarg("values", Top.Datum.Num)), 106 | fun(Top.Datum.Num)(arg("value", Top.Datum.Num)), 107 | fun("+", Top.Datum.Num)(arg("value", Top.Datum.Num)) 108 | ), 109 | 110 | // SUB = 25; // NUMBER... -> NUMBER 111 | module(termType = 25, name = "sub")(Top.Datum.Num)( 112 | fun(multiarg("values", Top.Datum.Num)), 113 | fun(Top.Datum.Num)(arg("value", Top.Datum.Num)), 114 | fun("-", Top.Datum.Num)(arg("value", Top.Datum.Num)) 115 | ), 116 | 117 | module(termType = 57, name = "dbCreate")(Top.Datum.Obj)( 118 | fun(arg("name", Top.Datum.Str)) 119 | ), 120 | 121 | // DB_LIST = 59; // -> ARRAY 122 | module(termType = 59, name = "dbList")(Top.Datum.Arr)(fun()), 123 | 124 | // * Data Operators 125 | // Returns a reference to a database. 126 | // STRING -> Database 127 | module(termType = 14, name = "db", doc = 128 | """ 129 | |Reference a database. 130 | |Example: Explicitly specify a database for a query. 131 | |{{{ 132 | |r.db('heroes').table('marvel').run(conn, callback) 133 | |}}} 134 | """.stripMargin 135 | )(Top.Database)( 136 | fun(arg("name", Top.Datum.Str)) 137 | ), 138 | 139 | // TABLE = 15; 140 | // Database, STRING, {read_mode:STRING, identifier_format:STRING} -> Table 141 | // STRING, {read_mode:STRING, identifier_format:STRING} -> Table 142 | module(termType = 15, name = "table", doc = 143 | """ 144 | |Select all documents in a table. This command can be chained with other commands to do further processing on the data. 145 | |Example: Return all documents in the table 'marvel' of the default database. 146 | |{{{ 147 | |r.table('marvel').run(conn, callback) 148 | |}}} 149 | """.stripMargin 150 | )(Top.Sequence.Table)( 151 | fun(Top.Database)( 152 | arg("name", Top.Datum.Str), 153 | opt("read_mode", Top.Datum.Str), 154 | opt("identifier_format", Top.Datum.Str) 155 | ) 156 | ), 157 | 158 | /* 159 | TABLE_CREATE = 60; // Database, STRING, {primary_key:STRING, shards:NUMBER, replicas:NUMBER, primary_replica_tag:STRING} -> OBJECT 160 | // Database, STRING, {primary_key:STRING, shards:NUMBER, replicas:OBJECT, primary_replica_tag:STRING} -> OBJECT 161 | // STRING, {primary_key:STRING, shards:NUMBER, replicas:NUMBER, primary_replica_tag:STRING} -> OBJECT 162 | // STRING, {primary_key:STRING, shards:NUMBER, replicas:OBJECT, primary_replica_tag:STRING} -> OBJECT 163 | 164 | */ 165 | module(termType = 60, name = "tableCreate")(Top.Datum.Obj)( 166 | fun(Top.Database)( 167 | arg("name", Top.Datum.Str), 168 | opt("primary_key", Top.Datum.Str), 169 | opt("shards", Top.Datum.Num), 170 | opt("replicas", Top.Datum), 171 | opt("primary_replica_tag", Top.Datum.Str) 172 | ) 173 | ), 174 | 175 | // TABLE_DROP = 61; // Database, STRING -> OBJECT 176 | // STRING -> OBJECT 177 | module(termType = 61, name = "tableDrop")(Top.Datum.Obj)( 178 | fun(Top.Database)(arg("name", Top.Datum.Str)) 179 | ), 180 | 181 | // Called on a table, waits for that table to be ready for read/write operations. 182 | // Called on a database, waits for all of the tables in the database to be ready. 183 | // Returns the corresponding row or rows from the `rethinkdb.table_status` table. 184 | // WAIT = 177; // Table -> OBJECT 185 | // // Database -> OBJECT 186 | module(termType = 177, name = "waitToBeReady")(Top.Datum.Obj)( 187 | fun(Top.Sequence.Table)(), 188 | fun(Top.Database)() 189 | ), 190 | 191 | // INDEX_CREATE = 75; 192 | // Table, STRING, Function(1), {multi:BOOL} -> OBJECT 193 | module(termType = 75, name = "indexCreate")(Top.Datum.Obj)( 194 | fun(Top.Sequence.Table)( 195 | arg("name", Top.Datum.Str), 196 | opt("multi", Top.Datum.Bool), 197 | opt("geo", Top.Datum.Bool) 198 | ), 199 | fun(Top.Sequence.Table)( 200 | arg("name", Top.Datum.Str), 201 | arg("f", Top.FunctionArg(1)), 202 | opt("multi", Top.Datum.Bool), 203 | opt("geo", Top.Datum.Bool) 204 | ) 205 | ), 206 | 207 | // INDEX_WAIT = 140; // Table, STRING... -> ARRAY 208 | module(termType = 140, name = "indexWait")(Top.Datum.Arr)( 209 | fun(Top.Sequence.Table)(multiarg("indexies", Top.Datum.Str)) 210 | ), 211 | 212 | module(termType = 77, name = "indexList")(Top.Datum.Arr)( 213 | fun(Top.Sequence.Table)() 214 | ), 215 | 216 | module(termType = 62, name = "tableList")(Top.Datum.Arr)( 217 | fun(Top.Database)() 218 | ), 219 | 220 | // Gets a single element from a table by its primary or a secondary key. 221 | // Table, STRING -> SingleSelection | Table, NUMBER -> SingleSelection | 222 | // Table, STRING -> NULL | Table, NUMBER -> NULL | 223 | module(termType = 16, name = "get")(Top.Datum.SingleSelection)( 224 | fun(Top.Sequence.Table)(arg("key", Top.Datum)) 225 | ), 226 | 227 | // module(termType = 78, name = "getAll")(Top.Datum.Arr)( 228 | // fun(Top.Sequence.Table)(multiarg("keys", Top.Datum), opt("index", Top.Datum.Str)) 229 | // ), 230 | 231 | // Sequence -> BOOL 232 | module(termType = 86, name = "isEmpty")(Top.Datum.Bool)( 233 | fun(Top.Sequence)() 234 | ), 235 | 236 | // GET_FIELD = 31; // OBJECT, STRING -> DATUM 237 | // | Sequence, STRING -> Sequence 238 | module(termType = 31, name = "getField")(Top.AnyType)( 239 | fun(Top.Datum.Obj)(arg("field_name", Top.Datum.Str)) 240 | ), 241 | 242 | // Check whether an object contains all the specified fields, 243 | // or filters a sequence so that all objects inside of it 244 | // contain all the specified fields. 245 | //HAS_FIELDS = 32; // OBJECT, Pathspec... -> BOOL 246 | module(termType = 32, name = "hasFields")(Top.Datum.Bool)( 247 | fun(Top.Datum.Obj)(multiarg("fields", Top.Datum.Str)) 248 | ), 249 | 250 | // Calls a function on data 251 | //FUNCALL = 64; // Function(*), DATUM... -> DATUM 252 | module(termType = 64, name = "applyFunction")(Top.Datum)( 253 | fun(arg("f", Top.FunctionArg(1)), arg("value", Top.Datum)) 254 | ), 255 | 256 | //PLUCK = 33; // Sequence, Pathspec... -> Sequence | OBJECT, Pathspec... -> OBJECT 257 | module(termType = 33, name = "pluck")(Top.AnyType)( 258 | fun(Top.Sequence)(multiarg("cols", Top.Datum.Str)), 259 | fun(Top.Datum.Obj)(multiarg("cols", Top.Datum.Str)) 260 | ), 261 | 262 | module(termType = 38, name = "map", doc = 263 | """ 264 | |Transform each element of one or more sequences by applying a mapping function to them. If map is run with two or more sequences, it will iterate for as many items as there are in the shortest sequence. 265 | |Example: Return the first five squares. 266 | |{{{ 267 | |r.expr([1, 2, 3, 4, 5]).map(function (val) { 268 | | return val.mul(val); 269 | |}).run(conn, callback); 270 | |}}} 271 | |// Result passed to callback 272 | |[1, 4, 9, 16, 25] 273 | """.stripMargin 274 | )(Top.Sequence)( 275 | fun(Top.Sequence)(arg("f", Top.FunctionArg(1))) 276 | ), 277 | 278 | //WITHOUT = 34; // Sequence, Pathspec... -> Sequence | OBJECT, Pathspec... -> OBJECT 279 | module(termType = 34, name = "without", doc = 280 | """Get a subset of an object by selecting some attributes to discard, or 281 | |map that over a sequence. (Both unpick and without, polymorphic.) 282 | """.stripMargin)(Top.AnyType)( 283 | fun(Top.Datum)(multiarg("objects", Top.Datum.Str)) 284 | ), 285 | 286 | //MERGE = 35; // OBJECT... -> OBJECT | Sequence -> Sequence 287 | module(termType = 35, name = "merge")(Top.AnyType)( 288 | fun(Top.Datum)(multiarg("objects", Top.Datum)), 289 | fun(Top.Datum)(arg("f", Top.FunctionArg(1))) 290 | ), 291 | 292 | module(termType = 39, name = "filter", doc = 293 | """Filter a sequence with either a function or a shortcut 294 | |object (see API docs for details). The body of FILTER is 295 | |wrapped in an implicit `.default(false)`, and you can 296 | |change the default value by specifying the `default` 297 | |optarg. If you make the default `r.error`, all errors 298 | |caught by `default` will be rethrown as if the `default` 299 | |did not exist. 300 | """.stripMargin 301 | )(Top.Sequence)( 302 | // Sequence, Function(1), {default:DATUM} -> Sequence | 303 | // Sequence, OBJECT, {default:DATUM} -> Sequence 304 | fun(Top.Sequence)(arg("f", Top.FunctionArg(1)), opt("default", Top.Datum)), 305 | fun(Top.Sequence)(arg("x", Top.Datum), opt("default", Top.Datum)) 306 | ), 307 | 308 | // CONCAT_MAP = 40; // Sequence, Function(1) -> Sequence 309 | module(termType = 40, name = "concatMap", doc = 310 | "Map a function over a sequence and then concatenate the results together." 311 | )(Top.Sequence)( 312 | // Sequence, Function(1) -> Sequence 313 | fun(Top.Sequence)(arg("f", Top.FunctionArg(1))) 314 | ), 315 | 316 | //BRACKET = 170; // Sequence | OBJECT, NUMBER | STRING -> DATUM 317 | module(termType = 170, name = "apply")(Top.AnyType)( 318 | fun(Top.Datum)(arg("field", Top.Datum.Str)), 319 | fun(Top.Datum)(arg("i", Top.Datum.Num)) 320 | ), 321 | 322 | // LIMIT = 71; // Sequence, NUMBER -> Sequence 323 | module(termType = 71, name = "limit")(Top.Sequence)( 324 | fun(Top.Sequence)(arg("count", Top.Datum.Num)) 325 | ), 326 | 327 | // SKIP = 70; // Sequence, NUMBER -> Sequence 328 | module(termType = 70, name = "skip")(Top.Sequence)( 329 | fun(Top.Sequence)(arg("count", Top.Datum.Num)) 330 | ), 331 | 332 | // Updates all the rows in a selection. Calls its Function with the row 333 | // to be updated, and then merges the result of that call. 334 | // UPDATE = 53; // StreamSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> 335 | // OBJECT | 336 | // SingleSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT | 337 | // StreamSelection, OBJECT, {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT | 338 | // SingleSelection, OBJECT, {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT 339 | module(termType = 53, name = "update", doc = 340 | """Updates all the rows in a selection. Calls its Function with the row 341 | |to be updated, and then merges the result of that call. 342 | """.stripMargin 343 | )(Top.Datum.Obj)( 344 | fun(Top.Datum.SingleSelection)( 345 | arg("f", Top.FunctionArg(1)), 346 | opt("non_atomic", Top.Datum.Bool), 347 | opt("durability", Top.Datum.Str), 348 | opt("return_changes", Top.Datum.Bool) 349 | ), 350 | fun(Top.Sequence.StreamSelection)( 351 | arg("obj", Top.Datum.Obj), 352 | opt("non_atomic", Top.Datum.Bool), 353 | opt("durability", Top.Datum.Str), 354 | opt("return_changes", Top.Datum.Bool) 355 | ), 356 | fun(Top.Datum.SingleSelection)( 357 | arg("obj", Top.Datum.Obj), 358 | opt("non_atomic", Top.Datum.Bool), 359 | opt("durability", Top.Datum.Str), 360 | opt("return_changes", Top.Datum.Bool) 361 | ) 362 | ), 363 | 364 | // REPLACE = 55; 365 | // StreamSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT | 366 | // SingleSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT 367 | module(termType = 55, name = "replace", doc = 368 | """Replaces all the rows in a selection. Calls its Function with the row 369 | |to be replaced, and then discards it and stores the result of that call. 370 | """.stripMargin 371 | )(Top.Datum.Obj)( 372 | fun(Top.Datum.SingleSelection)( 373 | arg("f", Top.FunctionArg(1)), 374 | opt("non_atomic", Top.Datum.Bool), 375 | opt("durability", Top.Datum.Str), 376 | opt("return_changes", Top.Datum.Bool) 377 | ), 378 | fun(Top.Datum.SingleSelection)( 379 | arg("obj", Top.Datum.Obj), 380 | opt("non_atomic", Top.Datum.Bool), 381 | opt("durability", Top.Datum.Str), 382 | opt("return_changes", Top.Datum.Bool) 383 | ) 384 | ), 385 | 386 | // Table, OBJECT, {conflict:STRING, durability:STRING, return_changes:BOOL} -> OBJECT | 387 | // Table, Sequence, {conflict:STRING, durability:STRING, return_changes:BOOL} -> OBJECT 388 | module(termType = 56, name = "insert", doc = 389 | """Inserts into a table. If `conflict` is replace, overwrites 390 | |entries with the same primary key. If `conflict` is 391 | |update, does an update on the entry. If `conflict` is 392 | |error, or is omitted, conflicts will trigger an error. 393 | |{{{ 394 | |r.table("posts").insert({ 395 | | id: 1, 396 | | title: "Lorem ipsum", 397 | | content: "Dolor sit amet" 398 | |}).run(conn, callback) 399 | |}}} 400 | """.stripMargin 401 | )(Top.Datum.Obj)( 402 | fun(Top.Sequence.Table)( 403 | arg("data", Top.Datum.Obj), 404 | opt("conflict", Top.Datum.Str), 405 | opt("durability", Top.Datum.Str), 406 | opt("return_changes", Top.Datum.Bool) 407 | ), 408 | fun(Top.Sequence.Table)( 409 | arg("batch", Top.Sequence), 410 | opt("conflict", Top.Datum.Str), 411 | opt("durability", Top.Datum.Str), 412 | opt("return_changes", Top.Datum.Bool) 413 | ) 414 | ), 415 | 416 | // SEQUENCE, STRING -> GROUPED_SEQUENCE | SEQUENCE, FUNCTION -> GROUPED_SEQUENCE 417 | genGroupModule(144, "group"), 418 | genGroupModule(145, "sum"), 419 | genGroupModule(146, "avg"), 420 | genGroupModule(147, "min"), 421 | genGroupModule(148, "max"), 422 | 423 | module(termType = 152, name = "changes")(Top.Sequence.Stream)( 424 | fun(Top.Sequence)(opt("include_initial", Top.Datum.Bool), opt("include_states", Top.Datum.Bool)), 425 | fun(Top.Datum.SingleSelection)(opt("include_initial", Top.Datum.Bool), opt("include_states", Top.Datum.Bool)) 426 | ), 427 | 428 | //---------------------------------------------------- 429 | // 430 | // Work with time 431 | // 432 | //---------------------------------------------------- 433 | 434 | module(termType = 102, name = "toEpochTime", 435 | doc="Returns seconds since epoch in UTC given a time." 436 | )(Top.Datum.Num)( 437 | fun(Top.PseudoType.Time)() 438 | ), 439 | 440 | module(termType = 103, name = "now")(Top.PseudoType.Time)(fun()), 441 | module(termType = 104, name = "inTimezone", doc = 442 | """ 443 | |Puts a time into an ISO 8601 timezone. 444 | """.stripMargin)(Top.Datum)( 445 | fun(Top.PseudoType.Time)(arg("timezone", Top.Datum.Str)) 446 | ), 447 | module(termType = 105, name = "during", doc = 448 | """ 449 | |a.during(b, c) returns whether a is in the range [b, c] 450 | """.stripMargin)(Top.Datum.Bool)( 451 | fun(Top.PseudoType.Time)(arg("left", Top.PseudoType.Time), arg("right", Top.PseudoType.Time)) 452 | ), 453 | module(termType = 106, name = "date", doc = 454 | """ 455 | |Retrieves the date portion of a time. 456 | """.stripMargin)(Top.PseudoType.Time)( 457 | fun(Top.PseudoType.Time)() 458 | ), 459 | module(termType = 126, name = "timeOfDay", doc = 460 | """ 461 | |x.time_of_day == x.date - x 462 | """.stripMargin)(Top.Datum.Num)( 463 | fun(Top.PseudoType.Time)() 464 | ), 465 | module(termType = 127, name = "timezone", doc = 466 | """ 467 | |Returns the timezone of a time. 468 | """.stripMargin)(Top.Datum.Str)( 469 | fun(Top.PseudoType.Time)() 470 | ), 471 | 472 | // Append a single element to the end of an array (like `snoc`). 473 | // APPEND = 29; // ARRAY, DATUM -> ARRAY 474 | // Prepend a single element to the end of an array (like `cons`). 475 | // PREPEND = 80; // ARRAY, DATUM -> ARRAY 476 | module(termType = 29, name = "append")(Top.Arr)( 477 | fun(Top.Datum.Arr)(arg("x", Top.Datum)) 478 | ), 479 | module(termType = 80, name = "prepend")(Top.Arr)( 480 | fun(Top.Datum.Arr)(arg("x", Top.Datum)) 481 | ), 482 | //CONTAINS = 93; // Sequence, (DATUM | Function(1))... -> BOOL 483 | module(termType = 93, name = "contains")(Top.Datum.Bool)( 484 | fun(Top.Sequence)(multiarg("x", Top.Datum)), 485 | fun(Top.Sequence)(arg("f", Top.FunctionArg(1))) 486 | ), 487 | 488 | //SET_UNION = 90; // ARRAY, ARRAY -> ARRAY 489 | module(termType = 90, name = "setUnion")(Top.Arr)( 490 | fun(Top.Arr)(arg("x", Top.Arr)) 491 | ), 492 | 493 | //SET_DIFFERENCE = 91; // ARRAY, ARRAY -> ARRAY 494 | module(termType = 91, name = "setDifference")(Top.Arr)( 495 | fun(Top.Arr)(arg("x", Top.Arr)) 496 | ), 497 | 498 | // Deletes all the rows in a selection. 499 | //DELETE = 54; // StreamSelection, {durability:STRING, return_changes:BOOL} -> OBJECT | SingleSelection -> OBJECT 500 | module(termType = 54, name = "delete")(Top.Datum.Obj)( 501 | fun(Top.Sequence.StreamSelection)(opt("durability", Top.Datum.Str), opt("return_changes", Top.Datum.Bool)), 502 | fun(Top.Datum.SingleSelection)() 503 | ), 504 | 505 | module(termType = 128, name = "year")(Top.Datum.Num)(fun(Top.PseudoType.Time)()), 506 | module(termType = 129, name = "month")(Top.Datum.Num)(fun(Top.PseudoType.Time)()), 507 | module(termType = 130, name = "day")(Top.Datum.Num)(fun(Top.PseudoType.Time)()), 508 | module(termType = 131, name = "dayOfWeek")(Top.Datum.Num)(fun(Top.PseudoType.Time)()), 509 | module(termType = 132, name = "dayOfYear")(Top.Datum.Num)(fun(Top.PseudoType.Time)()), 510 | module(termType = 133, name = "hours")(Top.Datum.Num)(fun(Top.PseudoType.Time)()), 511 | module(termType = 134, name = "minutes")(Top.Datum.Num)(fun(Top.PseudoType.Time)()), 512 | module(termType = 135, name = "seconds")(Top.Datum.Num)(fun(Top.PseudoType.Time)()), 513 | 514 | module(termType = 136, name = "time", doc = 515 | """ 516 | |time(year, month, day[, hour, minute, second], timezone) 517 | | 518 | |Create a time object for a specific time. 519 | | 520 | |A few restrictions exist on the arguments: 521 | | 522 | | * year is an integer between 1400 and 9,999. 523 | | * month is an integer between 1 and 12. 524 | | * day is an integer between 1 and 31. 525 | | * hour is an integer. 526 | | * minutes is an integer. 527 | | * seconds is a double. Its value will be rounded to three decimal places (millisecond-precision). 528 | | * timezone can be 'Z' (for UTC) or a string with the format ±[hh]:[mm]. 529 | | 530 | |Example: 531 | | 532 | |Update the birthdate of the user "John" to November 3rd, 1986 UTC. 533 | |{{{ 534 | |r.table("user").get("John").update({birthdate: r.time(1986, 11, 3, 'Z')}) 535 | | .run(conn, callback) 536 | |}}} 537 | """.stripMargin)(Top.PseudoType.Time)( 538 | fun( 539 | arg("year", Top.Datum.Num), 540 | arg("month", Top.Datum.Num), 541 | arg("day", Top.Datum.Num), 542 | arg("timezone", Top.Datum.Str) 543 | ), 544 | fun( 545 | arg("year", Top.Datum.Num), 546 | arg("month", Top.Datum.Num), 547 | arg("day", Top.Datum.Num), 548 | arg("hour", Top.Datum.Num), 549 | arg("minutes", Top.Datum.Num), 550 | arg("seconds", Top.Datum.Num), 551 | arg("timezone", Top.Datum.Str) 552 | ) 553 | ), 554 | module(termType = 41, name = "orderBy")(Top.Sequence)( 555 | fun(Top.Sequence)(multiarg("field", Top.Datum.Str), opt("index", Top.Datum.Str)), 556 | fun(Top.Sequence)(multiarg("ordering", Top.Ordering), opt("index", Top.Ordering)) 557 | ), 558 | module(termType = 73, name = "asc")(Top.Ordering)( 559 | fun(arg("field", Top.Datum.Str)) 560 | ), 561 | module(termType = 74, name = "desc")(Top.Ordering)( 562 | fun(arg("field", Top.Datum.Str)) 563 | ), 564 | 565 | //COUNT = 43; // Sequence -> NUMBER | Sequence, DATUM -> NUMBER | Sequence, Function(1) -> NUMBER 566 | module(termType = 43, name = "count")(Top.Datum.Num)( 567 | fun(Top.Sequence)(), 568 | fun(Top.Sequence)(arg("value", Top.Datum)), 569 | fun(Top.Sequence)(arg("f", Top.FunctionArg(1))) 570 | ), 571 | 572 | //ARGS = 154; // ARRAY -> SPECIAL (used to splice arguments) 573 | module(termType = 154, name = "args")(Top.Datum)( 574 | fun(arg("value", Top.Datum)) 575 | ), 576 | 577 | //DEFAULT = 92; // Top, Top -> Top 578 | module(termType = 92, name = "default")(Top.AnyType)( 579 | fun(Top.Datum)(arg("value", Top)) 580 | ) 581 | 582 | /* 583 | IMPLEMENT ME 584 | 585 | // Constants for ISO 8601 days of the week. 586 | MONDAY = 107; // -> 1 587 | TUESDAY = 108; // -> 2 588 | WEDNESDAY = 109; // -> 3 589 | THURSDAY = 110; // -> 4 590 | FRIDAY = 111; // -> 5 591 | SATURDAY = 112; // -> 6 592 | SUNDAY = 113; // -> 7 593 | 594 | // Constants for ISO 8601 months. 595 | JANUARY = 114; // -> 1 596 | FEBRUARY = 115; // -> 2 597 | MARCH = 116; // -> 3 598 | APRIL = 117; // -> 4 599 | MAY = 118; // -> 5 600 | JUNE = 119; // -> 6 601 | JULY = 120; // -> 7 602 | AUGUST = 121; // -> 8 603 | SEPTEMBER = 122; // -> 9 604 | OCTOBER = 123; // -> 10 605 | NOVEMBER = 124; // -> 11 606 | DECEMBER = 125; // -> 12 607 | 608 | */ 609 | ) 610 | 611 | 612 | } 613 | -------------------------------------------------------------------------------- /project/ApiDsl.scala: -------------------------------------------------------------------------------- 1 | 2 | sealed trait Top 3 | 4 | object Top extends Top { 5 | 6 | val all = Seq( 7 | Top.Arr, Top.Database, Top.Datum, Top.Error, Top.Function, 8 | Top.Ordering, Top.Pathspec, Top.Sequence, Top.Datum.Bool, 9 | Top.Datum.Null, Top.Datum.Num, Top.Datum.Obj, Top.Datum.SingleSelection, 10 | Top.Datum.Str, Top.Sequence.Stream, Top.Sequence.StreamSelection, 11 | Top.Sequence.Table, Top.PseudoType.Time, Top.PseudoType.Binary 12 | ) 13 | 14 | case object AnyType extends Top 15 | 16 | sealed trait Datum extends Top 17 | 18 | sealed trait Sequence extends Top 19 | 20 | case object Arr extends Datum with Sequence 21 | 22 | case object Database extends Top 23 | 24 | case class FunctionArg(argsCount: Int) extends Top 25 | 26 | case object Function extends Top 27 | 28 | case object Ordering extends Top 29 | 30 | case object Pathspec extends Top 31 | 32 | case object Error extends Top 33 | 34 | object Datum extends Top { 35 | 36 | case object Null extends Datum 37 | 38 | case object Bool extends Datum 39 | 40 | case object Num extends Datum 41 | 42 | sealed trait Str extends Datum 43 | 44 | case object Str extends Str 45 | 46 | sealed trait Obj 47 | 48 | case object Obj extends Datum with Obj 49 | 50 | case object SingleSelection extends Datum with Obj 51 | 52 | val Arr = Top.Arr 53 | } 54 | 55 | object Sequence extends Sequence { 56 | 57 | val Arr = Top.Arr 58 | 59 | sealed trait Stream extends Sequence 60 | 61 | case object Stream extends Stream 62 | 63 | sealed trait StreamSelection extends Stream 64 | 65 | case object StreamSelection extends StreamSelection 66 | 67 | sealed trait Table extends StreamSelection 68 | 69 | case object Table extends Table 70 | } 71 | 72 | object PseudoType { 73 | 74 | case object Time extends Top 75 | 76 | case object Binary extends Top 77 | } 78 | } 79 | 80 | case class module(termType: Int, dataTypes: Seq[Top], name: String, funcs: Seq[fun], doc: Option[String]) 81 | 82 | object module { 83 | def apply(name: String, termType: Int)(dataTypes: Top*)(funcs: fun*): module = { 84 | module(termType, dataTypes, name, funcs, None) 85 | } 86 | def apply(name: String, termType: Int, doc: String)(dataTypes: Top*)(funcs: fun*): module = { 87 | module(termType, dataTypes, name, funcs, Some(doc)) 88 | } 89 | } 90 | 91 | case class fun(customName: Option[String], dependency: Option[Top], args: Seq[ArgOrOpt]) 92 | 93 | object fun { 94 | 95 | def apply(customName: String, dependency: Top)(args: ArgOrOpt*): fun = { 96 | fun(Some(customName), Some(dependency), args) 97 | } 98 | 99 | def apply(customName: String)(args: ArgOrOpt*): fun = { 100 | fun(Some(customName), None, args) 101 | } 102 | 103 | def apply(dependency: Top)(args: ArgOrOpt*): fun = { 104 | fun(None, Some(dependency), args) 105 | } 106 | 107 | def apply(args: ArgOrOpt*): fun = fun(None, None, args) 108 | } 109 | 110 | sealed trait ArgOrOpt { 111 | 112 | val name: String 113 | 114 | val tpe: Top 115 | 116 | def isMulti = false 117 | } 118 | 119 | case class arg(name: String, tpe: Top) extends ArgOrOpt 120 | 121 | case class multiarg(name: String, tpe: Top) extends ArgOrOpt { 122 | override def isMulti: Boolean = true 123 | } 124 | 125 | case class opt(name: String, tpe: Top) extends ArgOrOpt 126 | -------------------------------------------------------------------------------- /project/ApiGenerator.scala: -------------------------------------------------------------------------------- 1 | import sbt.{File, _} 2 | 3 | class ApiGenerator(modules: Seq[module]) extends (File ⇒ Seq[File]) { 4 | 5 | private[this] def nameToCamelCase(name: String, isClass: Boolean = false): String = { 6 | val parts = name.split("_").zipWithIndex map { 7 | case (part, i) if i == 0 && !isClass ⇒ part 8 | case (part, _) ⇒ part.charAt(0).toUpper + part.substring(1) 9 | } 10 | parts.mkString 11 | } 12 | 13 | private[this] def topToName(t: Top): String = t match { 14 | case Top.FunctionArg(n) ⇒ 15 | val args = (0 until n).map(_ ⇒ "Var").mkString(",") 16 | s"($args) => Function" 17 | case Top ⇒ "Top" 18 | case Top.Function ⇒ "Function" 19 | case Top.Arr ⇒ "Arr" 20 | case Top.Database ⇒ "Database" 21 | case Top.Datum ⇒ "Datum" 22 | case Top.Error ⇒ "Error" 23 | case Top.Ordering ⇒ "Ordering" 24 | case Top.Pathspec ⇒ "Pathspec" 25 | case Top.Sequence ⇒ "Sequence" 26 | // -- 27 | case Top.Datum.Bool ⇒ "Bool" 28 | case Top.Datum.Null ⇒ "Null" 29 | case Top.Datum.Num ⇒ "Num" 30 | case Top.Datum.Obj ⇒ "Obj" 31 | case Top.Datum.SingleSelection ⇒ "SingleSelection" 32 | case Top.Datum.Str ⇒ "Str" 33 | // -- 34 | case Top.Sequence.Stream ⇒ "Stream" 35 | case Top.Sequence.StreamSelection ⇒ "StreamSelection" 36 | case Top.Sequence.Table ⇒ "Table" 37 | // -- 38 | case Top.PseudoType.Time ⇒ "Time" 39 | case Top.PseudoType.Binary ⇒ "Binary" 40 | // -- 41 | case Top.AnyType ⇒ "AnyType" 42 | } 43 | 44 | private[this] def genFuncs(module: module, hasDep: Boolean) = { 45 | module.funcs.zipWithIndex map { case (func, i) ⇒ 46 | // Header 47 | val funcName = nameToCamelCase(func.customName.getOrElse(module.name), isClass = false) 48 | val argsDef = func.args.collect { 49 | case arg@multiarg(_, _: Top.FunctionArg) ⇒ 50 | val name = nameToCamelCase(arg.name) 51 | val tpe = topToName(arg.tpe) 52 | s"$name: ($tpe)*" 53 | case arg: multiarg ⇒ 54 | val name = nameToCamelCase(arg.name) 55 | val tpe = topToName(arg.tpe) 56 | s"$name: $tpe*" 57 | case arg: arg ⇒ 58 | val name = nameToCamelCase(arg.name) 59 | val tpe = topToName(arg.tpe) 60 | s"$name: $tpe" 61 | } 62 | val optsDef = func.args.collect { 63 | case arg: opt ⇒ 64 | val name = nameToCamelCase(arg.name) 65 | val tpe = topToName(arg.tpe) 66 | s"$name: $tpe = EmptyOption" 67 | } 68 | // Body 69 | val args = { 70 | val xs = func.args.collect { 71 | case arg(name, _) ⇒ 72 | val ccName = nameToCamelCase(name) 73 | "${extractJson(" + ccName + ")}" 74 | case multiarg(name, _) ⇒ 75 | val ccName = nameToCamelCase(name) 76 | "${" + ccName + ".map(extractJson).mkString(\", \")}" 77 | } 78 | if (hasDep) { 79 | if (xs.isEmpty) "${extractJson(self)}" 80 | else "${extractJson(self)}, " + xs.mkString(", ") 81 | } 82 | else xs.mkString(", ") 83 | } 84 | val opts = { 85 | val xs = func.args.collect { case x: opt ⇒ x } 86 | val genMap = s"""val opts = Map(${xs.map(x ⇒ s""" "${x.name}" -> ${nameToCamelCase(x.name)}""").mkString(",")})""" 87 | val genPrinter = """opts.filter(_._2 != EmptyOption).map{ case (k,v) => "\""+k+"\":"+extractJson(v)}""" 88 | "${" + s"""$genMap; $genPrinter.mkString(",") """ + "}" 89 | } 90 | val ret = module.dataTypes.map(topToName).mkString(" with ") 91 | val complexRet = { 92 | if (optsDef.nonEmpty) nameToCamelCase(module.name, isClass = true) + i 93 | else ret 94 | } 95 | val toStrWithoutOpts = { 96 | val xs = func.args.collect { 97 | case x: arg => 98 | val ccName = nameToCamelCase(x.name) 99 | ccName + " = ${" + ccName + ".toString}" 100 | case x: multiarg => 101 | val ccName = nameToCamelCase(x.name) 102 | ccName + " = [${" + ccName + ".mkString(\",\")}]" 103 | } 104 | "s\"" + funcName + "(" + xs.mkString(", ") + ")" + "\"" 105 | } 106 | 107 | val toStrDep = { 108 | if (hasDep) """self.toString + "." + """ 109 | else "" 110 | } 111 | 112 | val toStrWithOpts = { 113 | val xs = func.args.map { x => 114 | val ccName = nameToCamelCase(x.name) 115 | ccName + " = ${" + ccName + ".toString}" 116 | } 117 | "s\"" + funcName + "(" + xs.mkString(", ") + ")" + "\"" 118 | } 119 | val doc = { 120 | val lines = module.doc.fold("") { s ⇒ 121 | s.split("\n").map(" * " + _).mkString("\n") 122 | } 123 | s""" 124 | | /** 125 | |$lines 126 | | */ 127 | """.stripMargin 128 | } 129 | val withOps = if (optsDef.nonEmpty) { 130 | val optsDefStr = optsDef.mkString(", ") 131 | s""" def optargs($optsDefStr): $ret = new $ret { 132 | | val json = s\"\""[${module.termType},[$args],{$opts}]\"\"" 133 | | override def toString: String = $toStrDep$toStrWithOpts 134 | | } 135 | """.stripMargin 136 | } else { 137 | "" 138 | } 139 | val argsDefStr = { 140 | if (argsDef.isEmpty) "" 141 | else "(" + argsDef.mkString(", ") + ")" 142 | } 143 | s""" $doc 144 | | def $funcName$argsDefStr: $complexRet = new $complexRet { 145 | | lazy val json = s\"\""[${module.termType},[$args]]\"\"" 146 | | override def toString: String = $toStrDep$toStrWithoutOpts 147 | |$withOps 148 | | } 149 | """.stripMargin 150 | } 151 | } 152 | 153 | private[this] def genFuncResultTypes(module: module) = { 154 | module.funcs.zipWithIndex map { case (func, i) ⇒ 155 | val complexRet = nameToCamelCase(module.name, isClass = true) + i 156 | val ret = module.dataTypes.map(topToName).mkString(" with ") 157 | val optsDef = func.args.collect { 158 | case arg: opt ⇒ 159 | val name = nameToCamelCase(arg.name) 160 | val tpe = topToName(arg.tpe) 161 | s"$name: $tpe = EmptyOption" 162 | } 163 | if (optsDef.nonEmpty) { 164 | s""" 165 | | trait $complexRet extends $ret { 166 | | def optargs(${optsDef.mkString(", ")}): $ret 167 | | } 168 | |""".stripMargin 169 | } else "" 170 | } 171 | } 172 | 173 | private[this] def genOps() = { 174 | Top.all map { tpe ⇒ 175 | val someTpe = Some(tpe) 176 | val tpeName = topToName(tpe) 177 | val className = tpeName + "Ops" 178 | val (funTypes, funDefs) = { 179 | val pureModules = modules map { module ⇒ 180 | val newFuncs = module.funcs.filter(fun ⇒ fun.dependency == someTpe) 181 | module.copy(funcs = newFuncs) 182 | } 183 | val funTypes = pureModules.flatMap(genFuncResultTypes) 184 | val funDefs = pureModules.flatMap(genFuncs(_, hasDep = true)) 185 | (funTypes.mkString("\n"), funDefs.mkString("\n")) 186 | } 187 | ReqlFile(className + ".scala", 188 | s""" 189 | |package reql.dsl 190 | | 191 | |import reql.dsl.types._ 192 | | 193 | |// Generated code. Do not modify 194 | |object $className { 195 | |$funTypes 196 | |} 197 | | 198 | |final class $className(val self: $tpeName) extends AnyVal { 199 | |import $className._ 200 | |$funDefs 201 | |} 202 | """.stripMargin 203 | ) 204 | } 205 | } 206 | 207 | private[this] def genImplicits() = { 208 | val defs = { 209 | val xs = Top.all map { tpe ⇒ 210 | val name = topToName(tpe) 211 | val nameOps = name + "Ops" 212 | s" implicit def to$nameOps(x: $name): $nameOps = new $nameOps(x)" 213 | } 214 | xs.mkString("\n") 215 | } 216 | ReqlFile("ReqlTermOpsConversions.scala", 217 | s""" 218 | |package reql.dsl 219 | | 220 | |import reql.dsl.types._ 221 | | 222 | |// Generated code. Do not modify 223 | |trait ReqlTermOpsConversions { 224 | |$defs 225 | |} 226 | """.stripMargin 227 | ) 228 | } 229 | 230 | private[this] def genBaseOps() = { 231 | val (funTypes, funDefs) = { 232 | val pureModules = modules map { module ⇒ 233 | val newFuncs = module.funcs.filter(fun ⇒ fun.dependency.isEmpty) 234 | module.copy(funcs = newFuncs) 235 | } 236 | val funTypes = pureModules.flatMap(genFuncResultTypes) 237 | val funDefs = pureModules.flatMap(genFuncs(_, hasDep = false)) 238 | (funTypes.mkString("\n"), funDefs.mkString("\n")) 239 | } 240 | ReqlFile("ReqlTopLevelApi.scala", 241 | s""" 242 | |package reql.dsl 243 | | 244 | |import reql.dsl.types._ 245 | | 246 | |object ReqlTopLevelApi { 247 | |$funTypes 248 | |} 249 | | 250 | |// Generated code. Do not modify| 251 | |class ReqlTopLevelApi { 252 | |import ReqlTopLevelApi._ 253 | |$funDefs 254 | |} 255 | """.stripMargin 256 | ) 257 | } 258 | 259 | def apply(dir: File): Seq[File] = { 260 | val packageDir = dir / "reql" / "dsl" 261 | (genBaseOps() :: genImplicits() :: genOps().toList) map { rf ⇒ 262 | val file = packageDir / rf.name 263 | IO.write(file, rf.content) 264 | file 265 | } 266 | } 267 | } 268 | 269 | object ApiGenerator extends ApiGenerator(ApiDefinitions.modules) 270 | 271 | case class ReqlFile(name: String, content: String) 272 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.12 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.12") 2 | -------------------------------------------------------------------------------- /scala-reql-akka/src/main/scala/reql/akka/ReqlActor.scala: -------------------------------------------------------------------------------- 1 | package reql.akka 2 | 3 | import akka.actor.{Actor, ActorLogging, ActorRef} 4 | import akka.util.Timeout 5 | import reql.dsl.{Cursor, ReqlArg, ReqlContext, ReqlQueryException} 6 | 7 | import scala.annotation.tailrec 8 | import scala.collection.mutable 9 | import scala.concurrent.forkjoin.ThreadLocalRandom 10 | 11 | trait ReqlActor[Data] extends Actor with ReqlContext[Data] with ActorLogging { 12 | 13 | import ReqlActor._ 14 | import ReqlContext._ 15 | import RethinkDbConnectionActor._ 16 | 17 | /** 18 | * Time to wait for response from RethinkDB 19 | * 20 | * @return 21 | */ 22 | def queryTimeout: Timeout 23 | 24 | /** 25 | * The actor implements ReqlTcpConnection protocol. It can be 26 | * ReqlTcp connection itself or router for connection pool. 27 | * 28 | * @return 29 | */ 30 | def dbConnection: ActorRef 31 | 32 | //--------------------------------------------------------------------------- 33 | // 34 | // ReqlContext implementation 35 | // 36 | //--------------------------------------------------------------------------- 37 | 38 | def runCursorQuery[U](query: ReqlArg)(f: CursorCb[Data]): Unit = { 39 | self ! Message.RunCursorQuery(query, f) 40 | } 41 | 42 | def runAtomQuery[U](query: ReqlArg)(f: AtomCb[Data]): Unit = { 43 | self ! Message.RunAtomQuery(query, f) 44 | } 45 | 46 | //--------------------------------------------------------------------------- 47 | // 48 | // Private zone 49 | // 50 | //--------------------------------------------------------------------------- 51 | 52 | private[this] class CursorImpl(token: Long) extends Cursor[Data] { 53 | 54 | sealed trait CursorState 55 | 56 | case object Idle extends CursorState 57 | 58 | case class Next(f: Either[ReqlQueryException, Data] ⇒ _) extends CursorState 59 | 60 | case class Force(f: Either[ReqlQueryException, Seq[Data]] ⇒ _) extends CursorState 61 | 62 | case class Foreach(f: Either[ReqlQueryException, Data] ⇒ _) extends CursorState 63 | 64 | case class Failed(e: ReqlQueryException) extends CursorState 65 | 66 | var state: CursorState = Idle 67 | 68 | var data = List.empty[Data] 69 | 70 | var closed = false 71 | 72 | def next[U](f: Either[ReqlQueryException, Data] ⇒ U): Unit = { 73 | checkFail(whenFail = e ⇒ f(Left(e))) { 74 | if (closed) { 75 | f(Left(ReqlQueryException.End)) 76 | } 77 | else { 78 | data match { 79 | case x :: xs ⇒ 80 | data = xs 81 | f(Right(x)) 82 | case Nil ⇒ 83 | state = Next(f) 84 | } 85 | dbConnection ! ContinueQuery(token) 86 | } 87 | } 88 | } 89 | 90 | def close(): Unit = { 91 | if (!closed) { 92 | closed = true 93 | dbConnection ! StopQuery(token) 94 | } 95 | else throw CursorException("Cursor already closed") 96 | } 97 | 98 | def forget(): Unit = { 99 | closed = true 100 | dbConnection ! ForgetQuery(token) 101 | } 102 | 103 | def force[U](f: Either[ReqlQueryException, Seq[Data]] ⇒ U): Unit = { 104 | checkFail(whenFail = e ⇒ f(Left(e))) { 105 | if (closed) f(Right(data.reverse)) 106 | else state = Force(f) 107 | } 108 | } 109 | 110 | def foreach[U](f: Either[ReqlQueryException, Data] ⇒ U): Unit = { 111 | checkFail(whenFail = e ⇒ f(Left(e))) { 112 | if (data != Nil) { 113 | data.foreach(x ⇒ f(Right(x))) 114 | data = Nil 115 | } 116 | if (!closed) { 117 | state = Foreach(f) 118 | dbConnection ! ContinueQuery(token) 119 | } 120 | } 121 | } 122 | 123 | /** 124 | * Check cursor is idle and not failed 125 | */ 126 | def checkFail[U1, U2](whenFail: ReqlQueryException ⇒ U1)(whenNot: ⇒ U2): Unit = { 127 | state match { 128 | case Failed(e) ⇒ whenFail(e) 129 | case Idle ⇒ whenNot 130 | case _ ⇒ 131 | val msg = s"Cursor already activated: $state" 132 | val e = ReqlQueryException.ReqlIllegalUsage(msg) 133 | whenFail(e) 134 | } 135 | } 136 | 137 | def fail(exception: ReqlQueryException): Unit = state match { 138 | case Idle ⇒ state = Failed(exception) 139 | case Foreach(f) ⇒ f(Left(exception)) 140 | case Next(f) ⇒ f(Left(exception)) 141 | case Force(f) ⇒ f(Left(exception)) 142 | case Failed(e) ⇒ // We cant fail twice! 143 | log.warning(s"Failed again: ${e.toString}") 144 | } 145 | 146 | def append(value: Data): Unit = { 147 | state match { 148 | case Next(f) ⇒ f(Right(value)) 149 | case Foreach(f) ⇒ f(Right(value)) 150 | case _ ⇒ data ::= value 151 | } 152 | } 153 | 154 | def closeAndNotifyEnd(): Unit = { 155 | closed = true 156 | state match { 157 | case Next(f) ⇒ f(Left(ReqlQueryException.End)) 158 | case Foreach(f) ⇒ f(Left(ReqlQueryException.End)) 159 | case Force(f) ⇒ f(Right(data.reverse)) 160 | case _ ⇒ // Do nothing 161 | } 162 | } 163 | } 164 | 165 | private[this] object Message { 166 | 167 | case class RunCursorQuery(query: ReqlArg, callback: CursorCb[Data]) 168 | 169 | case class RunAtomQuery(query: ReqlArg, callback: AtomCb[Data]) 170 | 171 | } 172 | 173 | private[this] val atomCallbacks = mutable.Map.empty[Long, AtomCb[Data]] 174 | 175 | private[this] val cursorCallbacks = mutable.Map.empty[Long, CursorCb[Data]] 176 | 177 | private[this] val activeCursors = mutable.Map.empty[Long, CursorImpl] 178 | 179 | @tailrec 180 | private[this] def appendSequenceToCursorAndForget(cursor: CursorImpl, tail: List[Data]): Unit = tail match { 181 | case Nil ⇒ 182 | cursor.closeAndNotifyEnd() 183 | cursor.forget() 184 | case x :: xs ⇒ 185 | cursor.append(x) 186 | appendSequenceToCursorAndForget(cursor, xs) 187 | } 188 | 189 | private[this] def registerCursorForPartialResponse(token: Long): CursorImpl = { 190 | val cursor = new CursorImpl(token) 191 | cursorCallbacks.remove(token) match { 192 | case Some(cb) ⇒ cb(cursor) 193 | case None ⇒ log.warning("Haven't callback for cursor with token {}", token) 194 | } 195 | cursor 196 | } 197 | 198 | private def Random = ThreadLocalRandom.current() 199 | 200 | override def unhandled(message: Any): Unit = { 201 | message match { 202 | case Message.RunCursorQuery(query, callback) ⇒ 203 | val token = Random.nextLong() 204 | val sendMessage = StartQuery(token, query) 205 | cursorCallbacks(token) = callback 206 | dbConnection ! sendMessage 207 | case Message.RunAtomQuery(query, callback) ⇒ 208 | val token = Random.nextLong() 209 | val sendMessage = StartQuery(token, query) 210 | atomCallbacks(token) = callback 211 | dbConnection ! sendMessage 212 | case RethinkDbConnectionActor.Response(token, rawData) ⇒ 213 | parseResponse(rawData) match { 214 | case pr: ParsedResponse.Atom[Data] ⇒ 215 | atomCallbacks.remove(token).foreach(cb ⇒ cb(Right(pr.data))) 216 | dbConnection ! ForgetQuery(token) 217 | case pr: ParsedResponse.Sequence[Data] if pr.partial ⇒ 218 | val cursor = activeCursors.getOrElseUpdate(token, registerCursorForPartialResponse(token)) 219 | pr.xs.foreach(cursor.append) 220 | dbConnection ! ContinueQuery(token) 221 | case pr: ParsedResponse.Sequence[Data] if !pr.partial ⇒ 222 | def xs = pr.xs.toList 223 | activeCursors.remove(token) match { 224 | case Some(cursor) ⇒ 225 | // A STOP query should be sent only to close a cursor that hasn't retrieved all his data yet. 226 | appendSequenceToCursorAndForget(cursor, xs) 227 | case None ⇒ cursorCallbacks.remove(token) match { 228 | case Some(cb) ⇒ 229 | val cursor = new CursorImpl(token) 230 | appendSequenceToCursorAndForget(cursor, xs) 231 | cb(cursor) 232 | case None ⇒ 233 | log.warning("Haven't a callback for non-partial response. Token is {}", token) 234 | } 235 | } 236 | case pr: ParsedResponse.Error ⇒ 237 | val ex = ReqlQueryException.ReqlErrorResponse(pr.tpe, pr.text) 238 | dbConnection ! ForgetQuery(token) 239 | activeCursors.remove(token) match { 240 | case Some(cursor) ⇒ 241 | cursor.fail(ex) 242 | cursorCallbacks.remove(token) 243 | case None ⇒ 244 | cursorCallbacks.remove(token) match { 245 | case Some(cb) ⇒ 246 | val cursor = new CursorImpl(token) 247 | cursor.fail(ex) 248 | cb(cursor) 249 | case None ⇒ 250 | atomCallbacks.remove(token) match { 251 | case Some(cb) ⇒ cb(Left(ex)) 252 | case None ⇒ log.warning("Haven't a callback for error response. Token is {}", token) 253 | } 254 | } 255 | } 256 | } 257 | case RethinkDbConnectionActor.ConnectionClosed ⇒ 258 | activeCursors foreach { 259 | case (k, v) ⇒ 260 | v.fail(ReqlQueryException.ConnectionError) 261 | } 262 | atomCallbacks foreach { 263 | case (k, v) ⇒ 264 | v(Left(ReqlQueryException.ConnectionError)) 265 | } 266 | cursorCallbacks foreach { 267 | case (k, v) ⇒ 268 | val cursor = new CursorImpl(k) 269 | cursor.fail(ReqlQueryException.ConnectionError) 270 | v(cursor) 271 | } 272 | cursorCallbacks.clear() 273 | atomCallbacks.clear() 274 | activeCursors.clear() 275 | case _ ⇒ 276 | super.unhandled(message) 277 | } 278 | } 279 | } 280 | 281 | object ReqlActor { 282 | case class CursorException(message: String) extends Exception(message) 283 | } 284 | -------------------------------------------------------------------------------- /scala-reql-akka/src/main/scala/reql/akka/RethinkDbConnectionActor.scala: -------------------------------------------------------------------------------- 1 | package reql.akka 2 | 3 | import akka.actor.{Actor, ActorLogging, ActorRef, Props, Terminated} 4 | import akka.io.{IO, Tcp} 5 | import akka.util.ByteString 6 | import java.net.InetSocketAddress 7 | import java.nio.ByteBuffer 8 | import java.nio.charset.StandardCharsets 9 | 10 | import reql.dsl.ReqlArg 11 | import reql.protocol.RethinkDbConnection 12 | 13 | import scala.collection.mutable 14 | import scala.concurrent.duration._ 15 | 16 | object RethinkDbConnectionActor { 17 | 18 | // Events 19 | 20 | sealed trait ReqlConnectionEvent 21 | case class Response(token: Long, data: Array[Byte]) 22 | extends ReqlConnectionEvent 23 | case object ConnectionClosed extends ReqlConnectionEvent 24 | 25 | //------------------------------------------------ 26 | // Commands 27 | //------------------------------------------------ 28 | 29 | sealed trait ReqlConnectionCommand { 30 | def token: Long 31 | } 32 | 33 | case class ForgetQuery(token: Long) extends ReqlConnectionCommand 34 | case class ContinueQuery(token: Long) extends ReqlConnectionCommand 35 | case class StopQuery(token: Long) extends ReqlConnectionCommand 36 | case class StartQuery(token: Long, query: ReqlArg) 37 | extends ReqlConnectionCommand 38 | 39 | def props(remote: InetSocketAddress, 40 | authKey: Option[String] = None, 41 | reconnectDelay: FiniteDuration = 5 seconds) = { 42 | Props(classOf[RethinkDbConnectionActor], 43 | remote, 44 | authKey, 45 | reconnectDelay) 46 | } 47 | } 48 | 49 | /** 50 | * Establish TCP connection to RethinkDB. Use RethinkDbConnectionActor 51 | * protocol to execute queries and process results 52 | * 53 | * @author Aleksey Fomkin 54 | */ 55 | class RethinkDbConnectionActor(remote: InetSocketAddress, 56 | authKey: Option[String], 57 | reconnectDelay: FiniteDuration) 58 | extends Actor 59 | with ActorLogging { 60 | 61 | import Tcp._ 62 | import RethinkDbConnectionActor._ 63 | import context.{dispatcher, system} 64 | 65 | var pendingCommands = List.empty[(ReqlConnectionCommand, ActorRef)] 66 | var workers = List.empty[ActorRef] 67 | var connectionAttempt = 0 68 | 69 | def establishConnection(firstTime: Boolean = false): Unit = { 70 | connectionAttempt += 1 71 | // Every attempt have double delay than previous 72 | val delay = 73 | if (firstTime) 0 seconds 74 | else reconnectDelay * connectionAttempt 75 | log.info(s"Try to establish RethinkDB connection in $delay") 76 | system.scheduler.scheduleOnce(delay) { 77 | IO(Tcp) ! Connect(remote) 78 | } 79 | } 80 | 81 | def receive = { 82 | case command: ReqlConnectionCommand => 83 | if (workers.nonEmpty) passCommand(command, sender()) 84 | else pendingCommands ::= (command, sender()) 85 | case CommandFailed(_: Connect) => 86 | log.error("Unable to connected.") 87 | establishConnection() 88 | case _: Connected => 89 | val tcpConnection = sender() 90 | val worker = { 91 | val props = RethinkDbConnectionWorkerActor.props( 92 | authKey, 93 | tcpConnection 94 | ) 95 | system.actorOf(props, "connection-worker") 96 | } 97 | // Reset connection attempts counter 98 | connectionAttempt = 0 99 | // Append worker 100 | context.watch(worker) 101 | workers = worker :: workers 102 | for ((command, sender) <- pendingCommands) { 103 | worker.tell(command, sender) 104 | } 105 | pendingCommands = Nil 106 | case Terminated(child) => 107 | if (workers.contains(child)) { 108 | workers = workers.filterNot(_ == child) 109 | establishConnection() 110 | } 111 | } 112 | 113 | def passCommand(command: ReqlConnectionCommand, sender: ActorRef): Unit = { 114 | workers foreach { worker => 115 | worker.tell(command, sender) 116 | } 117 | } 118 | 119 | // Try to connect to RethinkDB 120 | establishConnection(firstTime = true) 121 | } 122 | 123 | object RethinkDbConnectionWorkerActor { 124 | def props(authKey: Option[String], tcpConnection: ActorRef): Props = { 125 | Props(classOf[RethinkDbConnectionWorkerActor], authKey, tcpConnection) 126 | } 127 | } 128 | 129 | private class RethinkDbConnectionWorkerActor( 130 | authKey: Option[String], tcpConnection: ActorRef) 131 | extends Actor 132 | with ActorLogging 133 | with RethinkDbConnection { 134 | 135 | import Tcp._ 136 | import RethinkDbConnectionActor._ 137 | 138 | case class Ack(id: Long) extends Event 139 | case class SendBytes(data: ByteString) 140 | 141 | val mapping = mutable.Map.empty[Long, ActorRef] 142 | val storage = new mutable.Queue[ByteString]() 143 | 144 | var closing = false 145 | 146 | def processCommand(command: ReqlConnectionCommand, sender: ActorRef): Unit = { 147 | command match { 148 | case StartQuery(token, query) => 149 | mapping.put(token, sender) 150 | startQuery(token, query) 151 | case StopQuery(token) => 152 | mapping.remove(token) 153 | stopQuery(token) 154 | case ContinueQuery(token) => 155 | continueQuery(token) 156 | case ForgetQuery(token) => 157 | mapping.remove(token) 158 | } 159 | } 160 | 161 | var counter: Long = 0 162 | def receive: Receive = { 163 | case command: ReqlConnectionCommand => 164 | processCommand(command, sender()) 165 | case SendBytes(data) if storage.isEmpty => 166 | storage.enqueue(data) 167 | tcpConnection ! Write(data, Ack(counter)) 168 | case SendBytes(data) => storage.enqueue(data) 169 | case ack: Ack => acknowledge(ack) 170 | case Received(data) => processData(data.toByteBuffer) 171 | case PeerClosed => 172 | log.info("Connection closed by peer") 173 | mapping.values.foreach(_ ! ConnectionClosed) 174 | context stop self 175 | case Terminated(x) ⇒ log.error(s"$x is terminated") 176 | } 177 | 178 | private def acknowledge(ack: Ack): Unit = { 179 | if (ack.id == counter) { 180 | storage.dequeue() 181 | counter += 1 182 | 183 | storage.headOption match { 184 | case Some(nextData) ⇒ tcpConnection ! Write(nextData, Ack(counter)) 185 | case None if closing ⇒ 186 | log.info("Connection closed by peer") 187 | mapping.values.foreach(_ ! ConnectionClosed) 188 | context stop self 189 | case _ ⇒ 190 | } 191 | } else if (ack.id > counter) { 192 | log.warning(s"Expected ack with id ${ack.id}, but got $counter. Ignoring.") 193 | } // Ignore old queries acknowledge: ack.id < counter 194 | } 195 | 196 | protected def sendBytes(bytes: ByteBuffer): Unit = { 197 | bytes.position(0) 198 | val data = ByteString(bytes) 199 | self ! SendBytes(data) 200 | } 201 | 202 | protected def onFatalError(message: String): Unit = { 203 | log.error(message) 204 | context.stop(self) 205 | } 206 | 207 | protected def onResponse(token: Long, data: Array[Byte]): Unit = { 208 | mapping.get(token) match { 209 | case Some(receiver) => 210 | receiver ! Response(token, data) 211 | case None => 212 | // Sometimes https://github.com/rethinkdb/rethinkdb/issues/3296#issuecomment-79975399 is occurred 213 | log.warning(s"Received response for forgotten message: $token, ${new String(data, StandardCharsets.UTF_8)}") 214 | } 215 | } 216 | 217 | tcpConnection ! Register(self, keepOpenOnPeerClosed = true) 218 | sendBytes(createHandshakeBuffer(authKey)) 219 | context watch tcpConnection 220 | } 221 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/Cursor.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | trait Cursor[+Data] extends { 4 | 5 | def next[U](f: Either[ReqlQueryException, Data] ⇒ U): Unit 6 | 7 | def foreach[U](f: Either[ReqlQueryException, Data] ⇒ U): Unit 8 | 9 | def force[U](f: Either[ReqlQueryException, Seq[Data]] ⇒ U): Unit 10 | 11 | def close(): Unit 12 | } 13 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/NestedReqlContext.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | /** 4 | * @author Aleksey Fomkin 5 | */ 6 | trait NestedReqlContext[Data] extends ReqlContext[Data] { 7 | 8 | import ReqlContext._ 9 | 10 | def context: ReqlContext[Data] 11 | 12 | def parseResponse(data: Array[Byte]): ParsedResponse = { 13 | context.parseResponse(data) 14 | } 15 | 16 | def runCursorQuery[U](query: ReqlArg)(f: CursorCb[Data]): Unit = { 17 | context.runCursorQuery(query)(f) 18 | } 19 | 20 | def runAtomQuery[U](query: ReqlArg)(f: AtomCb[Data]): Unit = { 21 | context.runAtomQuery(query)(f) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/ReqlArg.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | trait ReqlArg { 4 | def json: String 5 | } 6 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/ReqlArrayDsl.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | import reql.dsl.types.Arr 4 | 5 | class ReqlArrayDsl { 6 | 7 | val empty = new Arr { 8 | val json = s"[2, []]" 9 | 10 | override def toString = "[]" 11 | } 12 | 13 | def apply(args: ReqlArg*): Arr = new Arr { 14 | override def toString = "[" + args.mkString(", ") + "]" 15 | 16 | val json = s"[2, [${args.map(_.json).mkString(",")}]]" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/ReqlContext.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | import reql.protocol.ReqlResponseWithError 4 | 5 | import scala.concurrent.{Future, Promise} 6 | import scala.language.implicitConversions 7 | 8 | /** 9 | * Base trait for any classes who want to implement 10 | * hi-level RethinkDB access. 11 | * @tparam Data JSON AST base trait or primitive 12 | */ 13 | trait ReqlContext[Data] extends ReqlEntryPoint { 14 | 15 | import ReqlContext._ 16 | 17 | implicit val runContext = this 18 | 19 | //--------------------------------------------------------------------------- 20 | // 21 | // Internal types 22 | // 23 | //--------------------------------------------------------------------------- 24 | 25 | /** 26 | * Parse response. Implement this in parser. 27 | * @param data JSON part of response 28 | */ 29 | def parseResponse(data: Array[Byte]): ParsedResponse 30 | 31 | def runCursorQuery[U](query: ReqlArg)(f: CursorCb[Data]): Unit 32 | 33 | def runAtomQuery[U](query: ReqlArg)(f: AtomCb[Data]): Unit 34 | 35 | //--------------------------------------------------------------------------- 36 | // 37 | // run() method providers 38 | // 39 | //--------------------------------------------------------------------------- 40 | 41 | implicit def toRunOps(x: ReqlArg): RunReqlOps[Data] = { 42 | new RunReqlOps(x) 43 | } 44 | 45 | } 46 | 47 | object ReqlContext { 48 | 49 | sealed trait ParsedResponse 50 | 51 | object ParsedResponse { 52 | 53 | trait Error extends ParsedResponse { 54 | def tpe: ReqlResponseWithError 55 | def text: String 56 | } 57 | 58 | trait Atom[Data] extends ParsedResponse { 59 | def data: Data 60 | } 61 | 62 | trait Sequence[Data] extends ParsedResponse { 63 | def xs: Seq[Data] 64 | def partial: Boolean 65 | } 66 | } 67 | 68 | type AtomCb[Data] = Either[ReqlQueryException, Data] ⇒ _ 69 | 70 | type CursorCb[Data] = Cursor[Data] ⇒ _ 71 | 72 | final class RunReqlOps[Data](val self: ReqlArg) extends AnyVal { 73 | 74 | def runA[U](f: Either[ReqlQueryException, Data] ⇒ U)(implicit c: ReqlContext[Data]): Unit = { 75 | c.runAtomQuery(self)(f) 76 | } 77 | 78 | def runA(implicit c: ReqlContext[Data]): Future[Data] = { 79 | val p = Promise[Data]() 80 | c.runAtomQuery[Unit](self) { res: Either[ReqlQueryException, Data] ⇒ 81 | res match { 82 | case Right(value) ⇒ p.success(value) 83 | case Left(value) ⇒ 84 | val exception = ThrowableReqlQueryException(value, self) 85 | p.failure(exception) 86 | } 87 | } 88 | p.future 89 | } 90 | 91 | def runC[U](f: Cursor[Data] ⇒ U)(implicit c: ReqlContext[Data]): Unit = { 92 | c.runCursorQuery(self)(f) 93 | } 94 | 95 | def runC(implicit c: ReqlContext[Data]): Future[Seq[Data]] = { 96 | val p = Promise[Seq[Data]]() 97 | c.runCursorQuery(self) { cursor: Cursor[Data] ⇒ 98 | cursor force { 99 | case Right(value) ⇒ p.success(value) 100 | case Left(value) ⇒ 101 | val exception = ThrowableReqlQueryException(value, self) 102 | p.failure(exception) 103 | } 104 | } 105 | p.future 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/ReqlDocumentDsl.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | import reql.dsl.types.Obj 4 | import scala.language.dynamics 5 | 6 | final class ReqlDocumentDsl extends Dynamic { 7 | 8 | val empty = new Obj { 9 | val json = s"[3, [], {}]" 10 | override def toString = "{}" 11 | } 12 | 13 | 14 | def fromSeq(args: Seq[(String, ReqlArg)]): Obj = new Obj { 15 | override def toString = { 16 | val s = args.map { case (k, v) ⇒ s"$k: $v" } 17 | s"{${s.mkString(", ")}}" 18 | } 19 | val json = { 20 | val optArgs = { 21 | val xs = args map { 22 | case (k, v) ⇒ s""""$k": ${v.json}""" 23 | } 24 | xs.mkString(",") 25 | } 26 | s"[3, [], {$optArgs}]" 27 | } 28 | } 29 | 30 | def fromMap(m: Map[String, ReqlArg]): Obj = fromSeq(m.toSeq) 31 | 32 | def applyDynamicNamed(method: String)(args: (String, ReqlArg)*): Obj = { 33 | fromSeq(args) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/ReqlEntryPoint.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | import reql.dsl.types.{Arr, Obj, Datum} 4 | 5 | import scala.language.{dynamics, implicitConversions} 6 | 7 | trait ReqlEntryPoint extends ReqlTermOpsConversions with ReqlTypesConversions { 8 | 9 | /** 10 | * The top-level ReQL namespace. 11 | */ 12 | val rethinkdb = new ReqlTopLevelApi() 13 | 14 | /** 15 | * The top-level ReQL namespace. Alias to [[rethinkdb]] 16 | */ 17 | val r = rethinkdb 18 | 19 | val Null = new Datum { 20 | override def toString = "null" 21 | val json = "null" 22 | } 23 | 24 | /** 25 | * Use it to create RethinkDB objects. 26 | * 27 | * val message = document( 28 | * author = document( 29 | * firstName = "John" 30 | * lastName = "Doe" 31 | * ), 32 | * timestamp = r.now(), 33 | * text = "Hello world" 34 | * ) 35 | */ 36 | val document = new ReqlDocumentDsl() 37 | 38 | /** 39 | * Use it to create Datum. 40 | * 41 | * val zoo = document( 42 | * manager = "John Doe", 43 | * animals = list("Cat", "Dog", "Duck") 44 | * ) 45 | */ 46 | val array = new ReqlArrayDsl() 47 | 48 | implicit def toTableSpecialOps(table: types.Table): TableSpecialOps = { 49 | new TableSpecialOps(table) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/ReqlQueryException.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | import reql.protocol.ReqlResponseWithError 4 | 5 | sealed trait ReqlQueryException 6 | 7 | object ReqlQueryException { 8 | 9 | case class ReqlIllegalUsage(text: String) 10 | extends ReqlQueryException 11 | 12 | case class ReqlErrorResponse(tpe: ReqlResponseWithError, text: String) 13 | extends ReqlQueryException 14 | 15 | /** 16 | * End of cursor 17 | */ 18 | case object End extends ReqlQueryException 19 | 20 | case object ConnectionError extends ReqlQueryException 21 | } 22 | 23 | case class ThrowableReqlQueryException(value: ReqlQueryException, 24 | query: ReqlArg) 25 | extends Throwable { 26 | 27 | override def toString: String = s"$value in $query. JSON: ${query.json}" 28 | 29 | override def getMessage: String = toString 30 | } 31 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/ReqlTypesConversions.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | import scala.annotation.switch 4 | import scala.language.implicitConversions 5 | 6 | trait ReqlTypesConversions { 7 | 8 | private def escape(sb: StringBuilder, s: String, unicode: Boolean): Unit = { 9 | sb.append('"') 10 | var i = 0 11 | val len = s.length 12 | while (i < len) { 13 | (s.charAt(i): @switch) match { 14 | case '"' => sb.append("\\\"") 15 | case '\\' => sb.append("\\\\") 16 | case '\b' => sb.append("\\b") 17 | case '\f' => sb.append("\\f") 18 | case '\n' => sb.append("\\n") 19 | case '\r' => sb.append("\\r") 20 | case '\t' => sb.append("\\t") 21 | case c => 22 | if (c < ' ' || (c > '~' && unicode)) sb.append("\\u%04x" format c.toInt) 23 | else sb.append(c) 24 | } 25 | i += 1 26 | } 27 | sb.append('"') 28 | } 29 | 30 | implicit def toStr(value: String): types.Str = new types.Str { 31 | override def toString = value 32 | val json = { 33 | val sb = new StringBuilder() 34 | escape(sb, value, unicode = true) 35 | sb.mkString 36 | } 37 | } 38 | 39 | implicit def toNum(value: Int): types.Num = new types.Num { 40 | override def toString = value.toString 41 | val json = value.toString 42 | } 43 | 44 | implicit def toNum(value: Float): types.Num = new types.Num { 45 | override def toString = value.toString 46 | val json = value.toString 47 | } 48 | 49 | implicit def toNum(value: Double): types.Num = new types.Num { 50 | override def toString = value.toString 51 | val json = value.toString 52 | } 53 | 54 | implicit def toBool(value: Boolean): types.Bool = new types.Bool { 55 | override def toString = value.toString 56 | val json = value.toString 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/TableSpecialOps.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | import types._ 4 | 5 | // GET_ALL = 78; // Table, DATUM..., {index:!STRING} => ARRAY 6 | final class TableSpecialOps(val self: Table) extends AnyVal { 7 | 8 | private def getArgs(keys: Seq[Datum]): String = { 9 | (self.json :: keys.map(extractJson).toList).mkString(",") 10 | } 11 | 12 | def getAll(index: Str)(keys: Datum*): Arr with Table = new Arr with Table { 13 | override def toString = self.toString + ".getAll(index = " + index + ", " + keys.mkString(", ") + ")" 14 | val json = { 15 | val args = getArgs(keys) 16 | s"""[78,[$args],{"index": ${extractJson(index)}}]""" 17 | } 18 | } 19 | 20 | def getAll(keys: Datum*): Arr with Table = new Arr with Table { 21 | override def toString = self.toString + ".getAll(" + keys.mkString(", ") + ")" 22 | val json = { 23 | val args = getArgs(keys) 24 | s"[78,[$args]]" 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/dsl/types.scala: -------------------------------------------------------------------------------- 1 | package reql.dsl 2 | 3 | /** 4 | * Definition of reql types of data. 5 | */ 6 | object types { 7 | 8 | trait AnyType extends ReqlArg with Top with Sequence with Database 9 | with Function with Obj with Null with Num with Str with Arr 10 | with Bool with Time with Ordering with Pathspec 11 | 12 | trait Top extends ReqlArg 13 | 14 | trait Datum extends Top with Function 15 | 16 | trait Sequence extends Top with CursorResultQuery with Function 17 | 18 | trait Arr extends Datum with Sequence 19 | 20 | trait Database extends Top 21 | 22 | trait Function extends Top 23 | 24 | trait Ordering extends Top 25 | 26 | trait Pathspec extends Top 27 | 28 | trait Error extends Top 29 | 30 | trait Null extends Datum with AtomResultQuery 31 | 32 | trait Bool extends Datum with AtomResultQuery 33 | 34 | trait Num extends Datum with AtomResultQuery 35 | 36 | trait Str extends Datum with AtomResultQuery 37 | 38 | trait Obj extends Datum with AtomResultQuery 39 | 40 | trait SingleSelection extends Datum with Obj with AtomResultQuery 41 | 42 | trait Stream extends Sequence 43 | 44 | trait StreamSelection extends Stream 45 | 46 | trait Table extends StreamSelection 47 | 48 | // Pseudo types 49 | 50 | trait Time extends Top 51 | 52 | trait Binary extends Top 53 | 54 | // Query types 55 | 56 | trait CursorResultQuery extends ReqlArg 57 | 58 | trait AtomResultQuery extends ReqlArg 59 | 60 | // Var 61 | 62 | object Var { 63 | def apply(n: Int): Var = new Var { 64 | override def toString = s"var:$n" 65 | val json = s"[10, [$n]]" 66 | } 67 | } 68 | 69 | trait Var extends AnyType 70 | 71 | object EmptyOption extends AnyType { 72 | override def toString = "?" 73 | val json: String = "" 74 | } 75 | 76 | def extractJson(from: ReqlArg): String = from.json 77 | 78 | def extractJson(from: (Var) ⇒ Function): String = { 79 | val body = from(Var(1)) 80 | s"[69, [[2, [1]], ${body.json}]]" 81 | } 82 | 83 | def extractJson(from: (Var, Var) ⇒ Function): String = { 84 | val body = from(Var(1), Var(2)) 85 | s"[69, [[2, [1, 2]], ${body.json}]]" 86 | } 87 | 88 | def extractJson(from: (Var, Var, Var) ⇒ Function): String = { 89 | val body = from(Var(1), Var(2), Var(3)) 90 | s"[69, [[2, [1, 2, 3]], ${body.json}]]" 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/protocol/ReqlProtocol.scala: -------------------------------------------------------------------------------- 1 | package reql.protocol 2 | 3 | sealed abstract class ReqlProtocol(val value: Int) 4 | 5 | object ReqlProtocol { 6 | 7 | case object ProtoBuf extends ReqlProtocol(656407617) 8 | 9 | case object Json extends ReqlProtocol(2120839367) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/protocol/ReqlQueryType.scala: -------------------------------------------------------------------------------- 1 | package reql.protocol 2 | 3 | sealed abstract class ReqlQueryType(val value: Int) 4 | 5 | object ReqlQueryType { 6 | 7 | /** 8 | * Start a new query. 9 | */ 10 | case object Start extends ReqlQueryType(1) 11 | 12 | /** 13 | * Continue a query that returned [SUCCESS_PARTIAL] 14 | */ 15 | case object Continue extends ReqlQueryType(2) 16 | 17 | /** 18 | * Stop a query partway through executing. 19 | */ 20 | case object Stop extends ReqlQueryType(3) 21 | 22 | /** 23 | * Wait for noreply operations to finish. 24 | */ 25 | case object NoReplyWait extends ReqlQueryType(4) 26 | } 27 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/protocol/ReqlResponseType.scala: -------------------------------------------------------------------------------- 1 | package reql.protocol 2 | 3 | sealed abstract class ReqlResponseType(val value: Int) 4 | 5 | sealed trait ReqlResponseWithError 6 | 7 | object ReqlResponseType { 8 | 9 | val enum = Seq( 10 | SuccessAtom, ClientError, SuccessPartial, 11 | SuccessSequence, CompileError, RuntimeError, 12 | WaitComplete 13 | ) 14 | 15 | def matchType(x: Int) = { 16 | enum.find(_.value == x).getOrElse(UnknownError) 17 | } 18 | 19 | /** 20 | * Query returned a single RQL datatype. 21 | */ 22 | case object SuccessAtom extends ReqlResponseType(1) 23 | 24 | /** 25 | * Query returned a sequence of RQL datatypes. 26 | */ 27 | case object SuccessSequence extends ReqlResponseType(2) 28 | 29 | /** 30 | * Query returned a partial sequence of RQL 31 | * datatypes. If you send a [CONTINUE] query with 32 | * the same token as this response, you will get 33 | * more of the sequence. Keep sending [CONTINUE] 34 | * queries until you get back [SUCCESS_SEQUENCE]. 35 | */ 36 | case object SuccessPartial extends ReqlResponseType(3) 37 | 38 | /** 39 | * A [NOREPLY_WAIT] query completed. 40 | */ 41 | case object WaitComplete extends ReqlResponseType(4) 42 | 43 | // These response types indicate failure. 44 | 45 | /** 46 | * Means the client is buggy. An example is if the 47 | * client sends a malformed protobuf, or tries to 48 | * send [CONTINUE] for an unknown token. 49 | */ 50 | case object ClientError extends ReqlResponseType(16) with ReqlResponseWithError 51 | 52 | /** 53 | * Means the query failed during parsing or type 54 | * checking. For example, if you pass too many 55 | * arguments to a function. 56 | */ 57 | case object CompileError extends ReqlResponseType(17) with ReqlResponseWithError 58 | 59 | /** 60 | * Means the query failed at runtime. An example is 61 | * if you add together two values from a table, but 62 | * they turn out at runtime to be booleans rather 63 | * than numbers. 64 | */ 65 | case object RuntimeError extends ReqlResponseType(18) with ReqlResponseWithError 66 | 67 | case object UnknownError extends ReqlResponseType(-1) with ReqlResponseWithError 68 | } 69 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/protocol/ReqlTcpConnection.scala: -------------------------------------------------------------------------------- 1 | package reql.protocol 2 | 3 | import java.nio.charset.StandardCharsets 4 | import java.nio.{ByteBuffer, ByteOrder} 5 | 6 | import reql.dsl.ReqlArg 7 | 8 | import scala.annotation.{switch, tailrec} 9 | 10 | /** 11 | * See specification at 12 | * http://www.rethinkdb.com/docs/writing-drivers/ 13 | */ 14 | trait RethinkDbConnection { 15 | 16 | import RethinkDbConnection._ 17 | 18 | //--------------------------------------------------------------------------- 19 | // 20 | // Public API 21 | // 22 | //--------------------------------------------------------------------------- 23 | 24 | /** 25 | * @param data Prepared query 26 | * @return Query unique token 27 | */ 28 | def startQuery(token: Long, data: ReqlArg): Long = { 29 | val json = s"[${ReqlQueryType.Start.value}, ${data.json}]" 30 | val jsonBuffer = ByteBuffer.wrap(json.getBytes("UTF-8")) 31 | send(token, jsonBuffer) 32 | token 33 | } 34 | 35 | def continueQuery(token: Long) = { 36 | send(token, ContinueBuffer) 37 | } 38 | 39 | def stopQuery(token: Long) = send(token, StopBuffer) 40 | 41 | //--------------------------------------------------------------------------- 42 | // 43 | // Internal API 44 | // 45 | //--------------------------------------------------------------------------- 46 | 47 | protected def sendBytes(data: ByteBuffer): Unit 48 | 49 | protected def onFatalError(message: String): Unit 50 | 51 | protected def onResponse(token: Long, data: Array[Byte]): Unit 52 | 53 | protected def reset(): Unit = { 54 | state = Handshake 55 | buffer = ByteBuffer.allocate(0) 56 | } 57 | 58 | /** 59 | * Process data from RethinkDB server when connection 60 | * was established and handshake was successful. 61 | * Decode packets from server and run callback. 62 | * 63 | * @param data Raw data from connection 64 | */ 65 | protected def processData(data: ByteBuffer): Unit = { 66 | // Update buffer 67 | buffer.position(0) 68 | buffer = ByteBuffer.allocate(buffer.capacity + data.capacity). 69 | put(buffer). 70 | put(data) 71 | processData() 72 | } 73 | 74 | @tailrec 75 | final protected def processData(): Unit = { 76 | buffer.position(0) 77 | buffer.order(ByteOrder.LITTLE_ENDIAN) 78 | // Process data 79 | (state: @switch) match { 80 | case Processing ⇒ 81 | // Enough for header 82 | if (buffer.capacity >= HeaderSize) { 83 | val queryToken = buffer.getLong(0) 84 | val responseLength = buffer.getInt(8) 85 | val messageSize = HeaderSize + responseLength 86 | // Enough for body 87 | if (buffer.capacity >= messageSize) { 88 | val jsonBytes = new Array[Byte](responseLength) 89 | buffer.position(HeaderSize) 90 | buffer.get(jsonBytes) 91 | // Truncate buffer 92 | buffer.position(messageSize) 93 | buffer = buffer.slice() 94 | // Process response 95 | onResponse(queryToken, jsonBytes) 96 | processData() 97 | } 98 | } 99 | case Handshake ⇒ 100 | val zeroByteIndex = 0 until buffer.capacity indexWhere { i ⇒ 101 | buffer.get(i) == 0 102 | } 103 | if (zeroByteIndex > -1) { 104 | val messageArray = new Array[Byte](zeroByteIndex) 105 | val messageSize = zeroByteIndex + 1 106 | buffer.position(0) 107 | buffer.get(messageArray, 0, zeroByteIndex) 108 | buffer.position(messageSize) 109 | buffer = buffer.slice() 110 | val asciiString = new String( 111 | messageArray, 112 | StandardCharsets.US_ASCII 113 | ) 114 | asciiString match { 115 | case "SUCCESS" ⇒ 116 | state = Processing 117 | processData() 118 | case res if res.startsWith("ERROR: ") ⇒ 119 | val s = asciiString.stripPrefix("ERROR: ") 120 | onFatalError(s"Handshake fails with: '$s'") 121 | case s ⇒ onFatalError(s"Unexpected handshake result: '$s'") 122 | } 123 | } 124 | } 125 | } 126 | 127 | /** 128 | * Prepare data for handshake 129 | */ 130 | protected def createHandshakeBuffer(authKey: Option[String], 131 | version: ReqlVersion = ReqlVersion.V04, 132 | protocol: ReqlProtocol = ReqlProtocol.Json): ByteBuffer = { 133 | val authKeyBuffer = authKey.fold(ByteBuffer.allocate(4)) { key ⇒ 134 | val buffer = ByteBuffer.allocate(key.length + 4). 135 | order(ByteOrder.LITTLE_ENDIAN). 136 | putInt(key.length). 137 | put(key.getBytes("ASCII")) 138 | buffer.position(0) 139 | buffer 140 | } 141 | val buffer = ByteBuffer.allocate(authKeyBuffer.capacity + 8). 142 | order(ByteOrder.LITTLE_ENDIAN). 143 | putInt(version.value). 144 | put(authKeyBuffer). 145 | putInt(protocol.value) 146 | buffer.position(0) 147 | buffer 148 | } 149 | 150 | //--------------------------------------------------------------------------- 151 | // 152 | // Private API 153 | // 154 | //--------------------------------------------------------------------------- 155 | 156 | private var state: Int = Handshake 157 | 158 | private var buffer: ByteBuffer = ByteBuffer.allocate(0) 159 | 160 | private def send(token: Long, data: ByteBuffer): Unit = { 161 | data.position(0) 162 | val buffer = ByteBuffer. 163 | allocate(HeaderSize + data.capacity). 164 | order(ByteOrder.LITTLE_ENDIAN). 165 | putLong(token). 166 | putInt(data.capacity). 167 | put(data) 168 | buffer.position(0) 169 | sendBytes(buffer) 170 | } 171 | } 172 | 173 | private object RethinkDbConnection { 174 | 175 | val Handshake = 0 176 | 177 | val Processing = 1 178 | 179 | val HeaderSize = 12 180 | 181 | val ContinueBuffer = ByteBuffer.wrap("[2]".getBytes("UTF-8")) 182 | 183 | val StopBuffer = ByteBuffer.wrap("[3]".getBytes("UTF-8")) 184 | } 185 | -------------------------------------------------------------------------------- /scala-reql-core/src/main/scala/reql/protocol/ReqlVersion.scala: -------------------------------------------------------------------------------- 1 | package reql.protocol 2 | 3 | sealed abstract class ReqlVersion(val value: Int) 4 | 5 | object ReqlVersion { 6 | 7 | case object V01 extends ReqlVersion(1063369270) 8 | 9 | case object V02 extends ReqlVersion(1915781601) 10 | 11 | case object V03 extends ReqlVersion(1601562686) 12 | 13 | case object V04 extends ReqlVersion(1074539808) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /scala-reql-pushka/src/main/scala/reql/pushka/PushkaReqlContext.scala: -------------------------------------------------------------------------------- 1 | package reql.pushka 2 | 3 | import java.nio.charset.StandardCharsets 4 | 5 | import pushka.annotation._ 6 | import pushka.json._ 7 | import pushka.{Ast, PushkaException} 8 | import reql.dsl.{types, ReqlEntryPoint, ReqlContext} 9 | import reql.dsl.types.Datum 10 | import reql.protocol.{ReqlResponseType, ReqlResponseWithError} 11 | 12 | import scala.language.implicitConversions 13 | 14 | trait PushkaReqlContext extends ReqlContext[Ast] { 15 | 16 | import ReqlResponseType._ 17 | import PushkaReqlContext._ 18 | import ReqlContext._ 19 | 20 | @pushka 21 | case class PRes(t: Int, r: Ast) 22 | 23 | case class Atom(data: Ast) extends ParsedResponse.Atom[Ast] 24 | 25 | case class Sequence(xs: Seq[Ast], partial: Boolean) extends ParsedResponse.Sequence[Ast] 26 | 27 | case class Error(tpe: ReqlResponseWithError, text: String) extends ParsedResponse.Error 28 | 29 | def parseResponse(data: Array[Byte]): ParsedResponse = { 30 | read[PRes](new String(data, StandardCharsets.UTF_8)) match { 31 | case PRes(ClientError.value, ast: Ast.Arr) ⇒ Error(ClientError, pushka.read[String](ast.value.head)) 32 | case PRes(CompileError.value, ast: Ast.Arr) ⇒ Error(CompileError, pushka.read[String](ast.value.head)) 33 | case PRes(RuntimeError.value, ast: Ast.Arr) ⇒ Error(RuntimeError, pushka.read[String](ast.value.head)) 34 | case PRes(SuccessAtom.value, ast: Ast.Arr) ⇒ Atom(ast.value.head) 35 | case PRes(SuccessPartial.value, ast: Ast.Arr) ⇒ Sequence(ast.value.toSeq, partial = true) 36 | case PRes(SuccessSequence.value, ast: Ast.Arr) ⇒ Sequence(ast.value.toSeq, partial = false) 37 | case response ⇒ throw new PushkaException(s"Unexpected response: $response") 38 | } 39 | } 40 | 41 | implicit def toAstOps(x: Ast): AstOps = new AstOps(x) 42 | } 43 | 44 | object PushkaReqlContext extends ReqlEntryPoint { 45 | 46 | def astToDatum(value: Ast): Datum = value match { 47 | case Ast.Obj(m) ⇒ 48 | document fromMap { 49 | m map { 50 | case (k, v) ⇒ (k, astToDatum(v)) 51 | } 52 | } 53 | case Ast.Str(s) ⇒ toStr(s) 54 | case Ast.Num(n) if n.contains('.') ⇒ toNum(n.toDouble) 55 | case Ast.Num(n) ⇒ toNum(n.toInt) 56 | case Ast.Arr(xs) ⇒ array(xs.toSeq.map(astToDatum):_*) 57 | case Ast.False ⇒ toBool(value = false) 58 | case Ast.True ⇒ toBool(value = true) 59 | case Ast.Null ⇒ Null 60 | } 61 | 62 | final class AstOps(val self: Ast) extends AnyVal { 63 | def toDatum: Datum = astToDatum(self) 64 | def toObj: types.Obj = self match { 65 | case Ast.Obj(m) ⇒ 66 | document fromMap { 67 | m map { 68 | case (k, v) ⇒ (k, astToDatum(v)) 69 | } 70 | } 71 | case _ ⇒ 72 | throw new ClassCastException(s"$self cannot be converted to reql.dsl.types.Obj") 73 | } 74 | } 75 | 76 | } 77 | --------------------------------------------------------------------------------