Example: Query Builder for SurrealDB
===
> [!WARNING]
> SurrealDB support is planned for v3. The current implementation is for reference only and may not work as expected in v2.

[back](../../README.md)

## index
<!--ts-->
* [Example: Query Builder for SurrealDB](#example-query-builder-for-surrealdb)
   * [index](#index)
   * [About SurrealDB](#about-surrealdb)
   * [Create Connection](#create-connection)
   * [SurrealId type](#surrealid-type)
   * [SELECT](#select)
      * [get](#get)
      * [first](#first)
      * [find](#find)
      * [columns](#columns)
      * [where](#where)
      * [fetch](#fetch)
   * [INSERT](#insert)
      * [insert single row](#insert-single-row)
      * [insert rows](#insert-rows)
      * [insertId](#insertid)
   * [UPDATE](#update)
   * [DELETE](#delete)
   * [delete all](#delete-all)
   * [delete row](#delete-row)
   * [Raw Query](#raw-query)
   * [Prepared Statement](#prepared-statement)
   * [Aggregates](#aggregates)
      * [count](#count)
      * [max](#max)
      * [min](#min)

<!-- Created by https://github.com/ekalinin/github-markdown-toc -->
<!-- Added by: root, at: Mon Jul 17 07:46:29 UTC 2023 -->

<!--te-->

---

## About SurrealDB
[SurrealDB official docs](https://surrealdb.com/docs)  
[SurrealDB Github](https://github.com/surrealdb/surrealdb)

SurrealDB is a next-generation database built on Rust that can handle all type of data structures-relational, document, and graph-and can run in-memory, on a single node, or in a distributed environment.  
It's response is JSON and allographer return as `JsonNode`.

## Create Connection
[to index](#index)

```nim
import allographer/connection

let maxConnections = 95
let timeout = 30
let surreal = dbOpen(SurrealDb, "test", "test", "user", "pass", "http://surreal", 8000, maxConnections, timeout, true, true).await()
```

## SurrealId type

The `SurrealId` type is used to handle SurrealDB Record IDs. It has the `table name` and `id` data.  
`insertId` returns `SurrealId` response and can be used as an argument to the `where` and `find` clauses.  

[Nim API document](https://itsumura-h.github.io/nim-allographer/query_builder/surreal/surreal_types.html#SurrealId)  
[SurrealDB Official document](https://surrealdb.com/docs/surrealql/datamodel/ids)

```nim
let aliceId = surreal.table("user").insertId(%*{"name": "alice"}).await
let aliceOpt = surreal.table("user").where("name", "=", "alice").first().await
let alice = aliceOpt.get()
check aliceId.rawId() == alice["id"].getStr()
check aliceId.table == alice["id"].getStr().split(":")[0]
check $aliceId == alice["id"].getStr().split(":")[1]

let alice2 = SurrealId.new(aliceId.rawId())
check alice2.rawId() == alice["id"].getStr()
check alice2.table == alice["id"].getStr().split(":")[0]
check $alice2 == alice["id"].getStr().split(":")[1]

let alice3 = SurrealId.new(aliceId.table, $aliceId)
check alice3.rawId() == alice["id"].getStr()
check alice3.table == alice["id"].getStr().split(":")[0]
check $alice3 == alice["id"].getStr().split(":")[1]
```

## SELECT
[to index](#index)

https://surrealdb.com/docs/surrealql/statements/select

When it returns following table

```sql
INSERT INTO `type` [
  {"bool":true,"string":"alice","int":1,"float":3.14,"datetime":"2023-06-05T11:58:42+00:00"},
  {"bool":false,"string":"bob","int":2,"float":1.11,"datetime":"2023-06-06T11:58:42+00:00"}
]
```

|bool|string|int|float|datetime|
|---|---|---|---|---|
|true|alice|1|3.14|2023-06-05T11:58:42+00:00
|false|bob|2|1.11|2023-06-06T11:58:42+00:00


### get
Retrieving all row from a table

```nim
import allographer/query_builder

echo surreal.table("user").get().await
```

```nim
DEBUG SELECT * FROM `type`

@[
  {
    "bool":true,
    "datetime":"2023-06-05T12:05:07Z",
    "float":3.14,
    "id":"type:9nxye3dons0juyelv5if",
    "int":1,
    "string":"alice"
    },
  {
    "bool":false,
    "datetime":"2023-06-06T12:05:07Z"
    ,"float":1.11,
    "id":"type:tw9iqdycdz9h1egtjkwb",
    "int":2,
    "string":"bob"
  }
]
```

### first
Retrieving a single row from a table. This returns `Option[JsonNode]`

```nim
let res = surreal.table("type").first().await
if res.isSome():
  echo firstRes.get()
```

```nim
DEBUG SELECT * FROM `type` LIMIT 1

{
  "bool":true,
  "datetime":"2023-06-05T12:08:47Z",
  "float":3.14,
  "id":"type:64t0w6gpnye7tdf083ch",
  "int":1,
  "string":"alice"
}
```

### find
Retrieve a single row by id. This returns `Option[JsonNode]`  
Id should be `SurrealId` type.  
https://itsumura-h.github.io/nim-allographer/query_builder/surreal/surreal_types.html#SurrealId

```nim
let id = SurrealId.new("type:64t0w6gpnye7tdf083ch")
let res = surreal.table("type").find(id).await
if res.isSome():
  echo res.get()
```

```nim
DEBUG SELECT * FROM `type` WHERE `id` = ? LIMIT 1 ["type:64t0w6gpnye7tdf083ch"]

{
  "bool":true,
  "datetime":"2023-06-05T12:08:47Z",
  "float":3.14,
  "id":"type:64t0w6gpnye7tdf083ch",
  "int":1,
  "string":"alice"
}
```

### columns
Retrieve columns from table.
```nim
let columns = surreal.table("users").columns().await
columns == @["email", "name", "id"]
```


### where
user
|id|name|email|
|---|---|---|
|user:2jrkvgwpdr02p71sfu1f|alice|alice@example.com|

```nim
let res = surreal.table("user").where("name", "=", "alice").first().await
if res.isSome():
  echo res.get()
```

```nim
{
  "email": "alice@example.com",
  "id": "user:iss5w0fp4x3o08t2br3s",
  "name": "alice"
}
```

### fetch
Fetch and replace records with the remote record data.  
It is used for retrieve relational tables.

auth
|id|name|
|---|---|
|auth:pl93k823yinrm8hzip22|admin|
|auth:e3is4h0txnn6cpmcuoca|editor|

user
|id|name|email|auth|
|---|---|---|---|
|user:2jrkvgwpdr02p71sfu1f|alice|alice@example.com|auth:pl93k823yinrm8hzip22|

```nim
let alice = surreal.table("user").where("auth.name", "=", "admin").fetch("auth").first().await
if alice.isSome():
  echo alice.get()
```

```nim
{
  "auth": {
    "id": "auth:ayp1w74u5oo8m4w2neyi",
    "name": "admin"
  },
  "email": "alice@example.com",
  "id": "user:5d7pgwag7i7uymigdako",
  "name": "alice"
}
```

## INSERT
[to index](#index)

https://surrealdb.com/docs/surrealql/statements/insert

### insert single row
```nim
surreal.table("type").insert(%*{
  "bool":true,
  "string": "alice",
  "int": 1,
  "float": 3.14,
  "datetime": now().format("yyyy-MM-dd'T'HH:mm:sszzz")
})
.await
```

### insert rows
```nim
surreal.table("type").insert(%*[
  {
    "bool":true,
    "string": "alice",
    "int": 1,
    "float": 3.14,
    "datetime": now().format("yyyy-MM-dd'T'HH:mm:sszzz")
  },
  {
    "bool":false,
    "string": "bob",
    "int": 2,
    "float": 1.11,
    "datetime": (now() + initDuration(days=1)).format("yyyy-MM-dd'T'HH:mm:sszzz")
  }
])
.await
```

### insertId
```nim
let id:SurrealId = surreal.table("type").insertId(%*{
  "bool":true,
  "string": "alice",
  "int": 1,
  "float": 3.14,
  "datetime": now().format("yyyy-MM-dd'T'HH:mm:sszzz")
})
.await

echo id.rawId()
```

```nim
type:9nxye3dons0juyelv5if
```

## UPDATE
[to index](#index)

https://surrealdb.com/docs/surrealql/statements/update

```nim
let id = surreal.table("type").insertId(%*{
  "bool":true,
  "string": "alice",
  "int": 1,
  "float": 3.14,
  "datetime": now().format("yyyy-MM-dd'T'HH:mm:sszzz")
})
.await

surreal.table("type").where("id", "=", id).update(%*{"string": "bob"}).await
let res = surreal.table("type").find(id).await
if res.isSome():
  echo res.get()
```

```nim
{
  "bool":true,
  "datetime":"2023-06-06T02:34:25Z",
  "float":3.14,
  "id":"type:hp0aqct78btlcyr1ho06",
  "int":1,
  "string":"bob"
}
```

## DELETE
[to index](#index)

https://surrealdb.com/docs/surrealql/statements/delete

## delete all
```nim
let id = surreal.table("type").insertId(%*{
  "bool":true,
  "string": "alice",
  "int": 1,
  "float": 3.14,
  "datetime": now().format("yyyy-MM-dd'T'HH:mm:sszzz")
})
.await

surreal.table("type").delete().await
echo surreal.table("type").get().await
```

```nim
@[]
```

## delete row
```nim
surreal.table("type").insert(%*[
  {
    "bool":true,
    "string": "alice",
    "int": 1,
    "float": 3.14,
    "datetime": now().format("yyyy-MM-dd'T'HH:mm:sszzz")
  },
  {
    "bool":false,
    "string": "bob",
    "int": 2,
    "float": 1.11,
    "datetime": (now() + initDuration(days=1)).format("yyyy-MM-dd'T'HH:mm:sszzz")
  }
])
.await

let row = surreal.table("type").where("string", "=", "alice").first().await.get()
let rowId = SurrealId.new(row["id"].getStr())

surreal.table("type").where("id", "=", rowId).delete().await
echo surreal.table("type").get().await
```

```nim
@[
  {
    "bool":false,
    "datetime":"2023-06-07T03:39:14Z",
    "float":"1.11",
    "id":"type:y93d6ig6iyrvcez0lc9k",
    "int":2,
    "string":"bob"
  }
]
```

## Raw Query
[to index](#INDEX)

`raw()` returns `RawQuerySurrealDb` type.  
You can use `get()`, `first()`, `exec()` and `info()` for raw query.  
`get()` returns all rows of result.  
`first()` returns first row of result.  
`exec()` executes query and not return any variables.  
`info()` executes query and returns all data of response.

```nim
let define = """
REMOVE TABLE type;
DEFINE TABLE type SCHEMAFULL;
DEFINE FIELD index ON TABLE type TYPE int;
DEFINE INDEX types_index ON TABLE type COLUMNS index UNIQUE;
DEFINE FIELD bool ON TABLE type TYPE bool;
DEFINE FIELD datetime ON TABLE type TYPE datetime;
DEFINE FIELD float ON TABLE type TYPE float;
DEFINE FIELD int ON TABLE type TYPE int;
DEFINE FIELD string ON TABLE type TYPE string;
"""
surreal.raw(define).exec().await()

echo surreal.raw("INFO FOR TABLE type").info().await

>> [
  {
    "time":"32.762515ms",
    "status":"OK",
    "result":{
      "ev":{},
      "fd":{
        "bool":"DEFINE FIELD bool ON type TYPE bool",
        "datetime":"DEFINE FIELD datetime ON type TYPE datetime",
        "float":"DEFINE FIELD float ON type TYPE float",
        "index":"DEFINE FIELD index ON type TYPE int",
        "int":"DEFINE FIELD int ON type TYPE int",
        "string":"DEFINE FIELD string ON type TYPE string"
      },
      "ft":{},
      "ix":{
        "types_index":"DEFINE INDEX types_index ON type FIELDS index UNIQUE"
      }
    }
  }
]

var rows = surreal.raw("SELECT * FROM type").get().await
let max = rows.len + 1

surreal.raw(
  """
    CREATE type CONTENT {
      index: ?,
      bool: true,
      datetime: ?,
      float: 1.11,
      int: 1,
      string: "aaa"
    }
  """,
  $max, now().format("yyyy-MM-dd'T'HH:mm:sszzz")
)
.exec()
.await

rows = surreal.raw("SELECT * FROM type").get().await
echo rows

>> @[
  {
    "bool":true,
    "datetime":"2023-06-06T04:01:32Z",
    "float":1.11,
    "id":"type:f1qxioacts8cydbmchpy",
    "index":1,
    "int":1,
    "string":"aaa"
  }
]
```

## Prepared Statement
[to index](#INDEX)

SurrealDB の prepared statement は [`documents/surrealdb/prepared_statement.md`](./prepared_statement.md) にまとめています。`prepare()`, `get()`, `first()`, `exec()`, `close()`, `flushStmt(stmt)`, `clearStmtCache()`, `withConn()` が利用できます。

```nim
let stmt = surreal.prepare("""SELECT * FROM "type" WHERE "id" = ?""")
let row = await stmt.first(@["type:9nxye3dons0juyelv5if"])
await stmt.close()
```

## Aggregates
[to index](#INDEX)

### count
```nim
surreal.table("user").insert(%*[
  {"name": "alice", "email": "alice@example.com"},
  {"name": "bob", "email": "bob@example.com"},
  {"name": "charlie", "email": "charlie@example.com"},
])
.await

let count = surreal.table("user").count().await
echo count
```

```sh
>> 3
```

### max
```nim
surreal.table("user").insert(%*[
  {"name": "alice", "email": "alice@example.com", "index": 1},
  {"name": "bob", "email": "bob@example.com", "index": 2},
  {"name": "charlie", "email": "charlie@example.com", "index": 3},
])
.await

let max = surreal.table("user").max("index").await
echo max
```

```
>> 3
```

### min
```nim
surreal.table("user").insert(%*[
  {"name": "alice", "email": "alice@example.com", "index": 1},
  {"name": "bob", "email": "bob@example.com", "index": 2},
  {"name": "charlie", "email": "charlie@example.com", "index": 3},
])
.await

let min = surreal.table("user").min("index").await
echo min
```

```
>> 1
```
