feat(clients): add nim-allographer client with BaraDB driver support
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled

- Pure Nim wire protocol client (async + sync, no C FFI)
- Query Builder: fluent API, SQL generator, execution, transactions
- Schema Builder: create/alter/drop tables, column operations
- Connection pool with aging and timeout handling
- ORM integration via compile-time DB_BARADB switch
- Tests: test_open, test_query
- Documentation: README.md, PLAN_BARADB.md
This commit is contained in:
2026-05-21 15:49:11 +03:00
parent 2d310a33a1
commit 6bfc5b3a3c
654 changed files with 109419 additions and 0 deletions
@@ -0,0 +1,2 @@
import os
putEnv("DB_BARADB", "true")
@@ -0,0 +1,6 @@
import std/asyncdispatch
import std/json
import ../../src/allographer/connection
import ../../src/allographer/query_builder
let baradb* = dbOpen(Baradb, "default", "admin", "", "127.0.0.1", 9472, maxConnections = 2, timeout = 30)
@@ -0,0 +1,13 @@
discard """
cmd: "nim c $file"
"""
import std/unittest
import ../../src/allographer/connection
import ../../src/allographer/query_builder
suite("Baradb connection"):
test("connection"):
let rdb = dbOpen(Baradb, "default", "admin", "", "127.0.0.1", 9472)
check(rdb.isConnected())
@@ -0,0 +1,82 @@
discard """
cmd: "nim c -r $file"
"""
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import std/strformat
import ../../src/allographer/query_builder
import ./connections
let rdb = baradb
suite("Baradb query"):
test("create table raw"):
let sql = """
CREATE TABLE IF NOT EXISTS "users" (
"id" SERIAL PRIMARY KEY,
"name" VARCHAR(255),
"email" VARCHAR(255),
"age" INTEGER
)
"""
waitFor rdb.raw(sql).exec()
check true
test("insert"):
waitFor rdb.table("users").insert(%*{ "name": "Alice", "email": "alice@example.com", "age": 30 })
waitFor rdb.table("users").insert(%*{ "name": "Bob", "email": "bob@example.com", "age": 25 })
check true
test("get"):
let users = waitFor rdb.select("id", "name", "email", "age").table("users").orderBy("id", Asc).get()
check users.len >= 2
check users[0]["name"].getStr == "Alice"
check users[0]["email"].getStr == "alice@example.com"
check users[0]["age"].getInt == 30
test("first"):
let user = waitFor rdb.table("users").where("name", "=", "Alice").first()
check user.isSome
check user.get["name"].getStr == "Alice"
test("find"):
let user = waitFor rdb.table("users").find(1)
check user.isSome
check user.get["id"].getInt == 1
test("update"):
waitFor rdb.table("users").where("name", "=", "Alice").update(%*{ "age": 31 })
let user = waitFor rdb.table("users").where("name", "=", "Alice").first()
check user.isSome
check user.get["age"].getInt == 31
test("delete"):
waitFor rdb.table("users").where("name", "=", "Bob").delete()
let user = waitFor rdb.table("users").where("name", "=", "Bob").first()
check user.isNone
test("count"):
let cnt = waitFor rdb.table("users").count()
check cnt >= 1
test("transaction rollback"):
waitFor rdb.begin()
waitFor rdb.table("users").insert(%*{ "name": "Charlie", "email": "charlie@example.com", "age": 40 })
waitFor rdb.rollback()
let user = waitFor rdb.table("users").where("name", "=", "Charlie").first()
check user.isNone
test("transaction commit"):
waitFor rdb.begin()
waitFor rdb.table("users").insert(%*{ "name": "Dave", "email": "dave@example.com", "age": 50 })
waitFor rdb.commit()
let user = waitFor rdb.table("users").where("name", "=", "Dave").first()
check user.isSome
test("raw select"):
let users = waitFor rdb.raw("SELECT * FROM \"users\" WHERE \"age\" > 20").get()
check users.len >= 1
@@ -0,0 +1,71 @@
import std/asyncdispatch
import std/json
import std/strformat
import std/strutils
import ../src/allographer/query_builder
proc clearTables*(rdb:PostgresConnections) {.async.} =
try:
let tables = rdb.table("_allographer_migrations").orderBy("index", Desc) .get().await
for table in tables:
let tableName = table["name"].getStr()
if not tableName.startsWith("_"):
rdb.raw(&"DROP TABLE IF EXISTS \"{tableName}\"").exec().await
rdb.raw("DROP TABLE IF EXISTS \"_allographer_migrations\"").exec().await
except:
echo "error: ", getCurrentExceptionMsg()
proc clearTables*(rdb:MariaDBConnections) {.async.} =
try:
let tables = rdb.table("_allographer_migrations").orderBy("index", Desc) .get().await
for table in tables:
let tableName = table["name"].getStr()
if not tableName.startsWith("_"):
rdb.raw(&"DROP TABLE IF EXISTS `{tableName}`").exec().await
rdb.raw("DROP TABLE IF EXISTS `_allographer_migrations`").exec().await
except:
echo "error: ", getCurrentExceptionMsg()
proc clearTables*(rdb:MySQLConnections) {.async.} =
try:
let tables = rdb.table("_allographer_migrations").orderBy("index", Desc) .get().await
for table in tables:
let tableName = table["name"].getStr()
if not tableName.startsWith("_"):
rdb.raw(&"DROP TABLE IF EXISTS `{tableName}`").exec().await
rdb.raw("DROP TABLE IF EXISTS `_allographer_migrations`").exec().await
except:
echo "error: ", getCurrentExceptionMsg()
proc clearTables*(rdb:SqliteConnections) {.async.} =
try:
let tables = rdb.table("_allographer_migrations").orderBy("index", Desc) .get().await
for table in tables:
let tableName = table["name"].getStr()
if not tableName.startsWith("_"):
rdb.raw(&"DROP TABLE IF EXISTS \"{tableName}\"").exec().await
rdb.raw("DROP TABLE IF EXISTS \"_allographer_migrations\"").exec().await
except:
echo "error: ", getCurrentExceptionMsg()
proc clearTables*(rdb:SurrealConnections) {.async.} =
try:
let dbInfo = rdb.raw("INFO FOR DB").info().await
let tables = dbInfo[0]["result"]["tables"]
for (table, _) in tables.pairs:
if not table.startsWith("_"):
rdb.raw(&"REMOVE TABLE {table}").exec().await
rdb.raw(&"REMOVE TABLE _allographer_migrations").exec().await
rdb.raw(&"REMOVE TABLE _autoincrement_sequences").exec().await
except:
echo "error: ", getCurrentExceptionMsg()
@@ -0,0 +1,8 @@
import os
switch("path", "$projectDir/../src")
putEnv("DB_SQLITE", $true)
putEnv("DB_POSTGRES", $true)
putEnv("DB_MYSQL", $true)
putEnv("DB_MARIADB", $true)
putEnv("DB_SURREAL", $true)
@@ -0,0 +1,11 @@
import std/os
import std/strutils
import ../../src/allographer/connection
let
mariadbUrl = getEnv("MARIA_URL")
mysqlUrl = getEnv("MY_URL")
maxConnections = getEnv("DB_MAX_CONNECTION").parseInt
timeout = getEnv("DB_TIMEOUT").parseInt
let mariadb* = dbOpen(MariaDB, databaseUrl = asDatabaseUrl(mariadbUrl), maxConnections, timeout, shouldDisplayLog=true)
@@ -0,0 +1,108 @@
discard """
cmd: "nim c -d:reset $file"
"""
# nim c -d:reset -r tests/mariadb/test_create_schema.nim
import std/unittest
import std/asyncdispatch
import std/os
import std/strutils
import std/json
import ../../src/allographer/schema_builder
import ./connections
import ../clear_tables
let rdb = mariadb
let schemaFilePath = getCurrentDir() / "schema.nim"
clearTables(rdb).waitFor()
suite "Schema output after migration":
setup:
# schema.nim ファイルの削除
if fileExists(schemaFilePath):
removeFile(schemaFilePath)
rdb.create(
table("int_relation", [
Column.increments("id")
]),
table("str_relation", [
Column.uuid("uuid")
]),
table("test_schema_output", [
Column.increments("id"),
Column.integer("integer"),
Column.smallInteger("smallInteger"),
Column.mediumInteger("mediumInteger"),
Column.bigInteger("bigInteger"),
Column.decimal("decimal", 10, 3),
Column.double("double", 10, 3),
Column.float("float"),
Column.uuid("uuid"),
Column.char("char", 255),
Column.string("string"),
Column.text("text"),
Column.mediumText("mediumText"),
Column.longText("longText"),
Column.date("date"),
Column.datetime("datetime"),
Column.time("time"),
Column.timestamp("timestamp"),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary"),
Column.boolean("boolean"),
Column.enumField("enumField", ["A", "B", "C"]),
Column.json("json"),
Column.foreign("int_relation_id").reference("id").onTable("int_relation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("str_relation").onDelete(SET_NULL)
])
)
test "should generate schema.nim file":
# スキーマ生成
rdb.createSchema(schemaFilePath).waitFor()
# schema.nim ファイルの存在確認
check fileExists(schemaFilePath)
# ファイル内容の検証
let schemaContent = readFile(schemaFilePath)
echo "schemaContent: ", schemaContent
check schemaContent.contains("type TestSchemaOutputTable* = object")
check schemaContent.contains("## test_schema_output")
check schemaContent.contains("integer*: int")
check schemaContent.contains("smallInteger*: int")
check schemaContent.contains("mediumInteger*: int")
check schemaContent.contains("bigInteger*: int")
check schemaContent.contains("decimal*: float")
check schemaContent.contains("double*: float")
check schemaContent.contains("float*: float")
check schemaContent.contains("uuid*: string")
check schemaContent.contains("char*: string")
check schemaContent.contains("string*: string")
check schemaContent.contains("text*: string")
check schemaContent.contains("mediumText*: string")
check schemaContent.contains("longText*: string")
check schemaContent.contains("date*: string")
check schemaContent.contains("datetime*: string")
check schemaContent.contains("time*: string")
check schemaContent.contains("timestamp*: string")
check schemaContent.contains("created_at*: string")
check schemaContent.contains("updated_at*: string")
check schemaContent.contains("deleted_at*: string")
check schemaContent.contains("binary*: string")
check schemaContent.contains("boolean*: bool")
check schemaContent.contains("enumField*: string")
check schemaContent.contains("json*: string")
check schemaContent.contains("int_relation_id*: int")
check schemaContent.contains("str_relation_id*: string")
# clearTables(rdb).waitFor()
# schema.nim ファイルの削除
if fileExists(schemaFilePath):
removeFile(schemaFilePath)
@@ -0,0 +1,45 @@
discard """
cmd: "nim c $file"
"""
# nim c -r tests/v2/mariadb/test_open.nim
import std/unittest
import ../../src/allographer/connection
import ../../src/allographer/query_builder
suite("MariaDB connection"):
test("connection"):
let database = "database"
let user = "user"
let password = "pass"
let host = "mariadb"
let port = 3306
let rdb = dbOpen(Mariadb, database, user, password, host, port)
check(rdb.isConnected())
test("connection with URL"):
let url = "mariadb://user:pass@mariadb:3306/database"
let rdb = dbOpen(Mariadb, url)
check(rdb.isConnected())
test("connection with DatabaseUrl"):
let url = asDatabaseUrl("mariadb://user:pass@mariadb:3306/database")
let rdb = dbOpen(MariaDB, databaseUrl = url)
check(rdb.isConnected())
test("url is not start with mariadb://"):
let url = "aaa://user:pass@mariadb:3306/database"
expect(ValueError):
discard dbOpen(Mariadb, url)
test("cannot reach to database"):
let url = "mariadb://john:password@host:0001/database"
expect(DbError):
discard dbOpen(Mariadb, url)
@@ -0,0 +1,29 @@
discard """
cmd: "nim c -d:ssl -r $file"
"""
# nim c -d:ssl r tests/mariadb/test_pool_wait.nim
import std/[asyncdispatch, deques, os, strutils, unittest]
import ../../src/allographer/connection
import ../../src/allographer/query_builder
suite "MariaDB pool waiter (notify)":
test "pool size 1: two concurrent raw gets both complete":
let mariaUrl = getEnv("MARIA_URL")
let timeout = getEnv("DB_TIMEOUT").parseInt
let rdb = dbOpen(MariaDB, mariaUrl, maxConnections = 1, timeout)
proc sel(): Future[void] {.async.} =
discard await rdb.raw("SELECT SLEEP(0.05), 1").get()
let a = sel()
let b = sel()
waitFor all(a, b)
check rdb.pools.waiters.len == 0
test "pool size 0: raw get raises DbError":
let mariaUrl = getEnv("MARIA_URL")
let rdb = dbOpen(MariaDB, mariaUrl, maxConnections = 0, timeout = 0)
expect(DbError):
discard waitFor rdb.raw("SELECT 1").get()
@@ -0,0 +1,149 @@
discard """
cmd: "nim c -d:reset -d:ssl -r $file"
"""
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import std/strformat
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
let rdb = mariadb
proc setup(rdb: MariadbConnections) =
rdb.create([
table("auth", [
Column.increments("id"),
Column.string("auth")
]),
table("user", [
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.date("submit_on").nullable(),
Column.datetime("submit_at").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL).nullable()
])
])
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
]).waitFor
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
let month = if i > 9: $i else: &"0{i}"
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId,
"submit_on": &"2020-{month}-01",
"submit_at": &"2020-{month}-01 00:00:00",
}
)
rdb.table("user").insert(users).waitFor
setup(rdb)
suite($rdb & " prepared statement"):
test("aged connection refresh"):
rdb.pools.maxConnectionLifetime = 1
rdb.pools.maxConnectionIdleTime = 1
for conn in rdb.pools.conns.mitems:
conn.createdAt = 0
conn.lastUsedAt = 0
let sql = """SELECT `id`, `name`, `email`, `address` FROM `user` WHERE `id` = ?"""
let stmt = rdb.prepare(sql)
defer:
waitFor stmt.close()
let rowOpt = stmt.first(@["1"]).waitFor
check rowOpt.isSome
var refreshed = false
for conn in rdb.pools.conns:
if conn.createdAt > 0:
refreshed = true
break
check refreshed
test("select"):
let stmt = rdb.prepare("""SELECT `id`, `name`, `email`, `address` FROM `user` WHERE `id` = ?""")
defer:
waitFor stmt.close()
let args = newJArray()
args.add(newJInt(1))
let rows = stmt.get(args).waitFor
check rows.len == 1
check rows[0] == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address": newJNull()}
let rowOpt = stmt.first(args).waitFor
let row = options.get(rowOpt)
check row["name"].getStr == "user1"
check stmt.getPlain(args).waitFor[0][1] == "user1"
check stmt.firstPlain(args).waitFor[1] == "user1"
test("logical close and clear cache"):
let sql = """SELECT `id`, `name`, `email`, `address` FROM `user` WHERE `id` = ?"""
let stmt = rdb.prepare(sql)
discard waitFor stmt.first(@["1"])
waitFor stmt.close()
let stmt2 = rdb.prepare(sql)
defer:
waitFor stmt2.close()
let rowOpt = stmt2.first(@["1"]).waitFor
check rowOpt.isSome
waitFor rdb.clearStmtCache()
let stmt3 = rdb.prepare(sql)
defer:
waitFor stmt3.close()
check stmt3.first(@["1"]).waitFor.isSome
test("with prepared connection context"):
let selectStmt = rdb.prepare("""SELECT `id`, `name` FROM `user` WHERE `id` = ?""")
let updateStmt = rdb.prepare("""UPDATE `user` SET `address` = ? WHERE `id` = ?""")
defer:
waitFor selectStmt.close()
waitFor updateStmt.close()
waitFor rdb.withConn(
proc(ctx: MariadbPreparedContext): Future[void] {.async.} =
discard await selectStmt.first(ctx, @["1"])
await updateStmt.exec(ctx, @["ctx-address", "1"])
)
let rowOpt = rdb.table("user").find(1).waitFor
let row = options.get(rowOpt)
check row["address"].getStr == "ctx-address"
test("update null"):
let stmt = rdb.prepare("""UPDATE `user` SET `address` = ? WHERE `id` = ?""")
defer:
waitFor stmt.close()
waitFor stmt.exec(@["NULL", "1"])
let rowOpt = rdb.table("user").find(1).waitFor
let row = options.get(rowOpt)
check row["address"].kind == JNull
@@ -0,0 +1,563 @@
discard """
cmd: "nim c -d:reset -d:ssl -r $file"
"""
# nim c -d:reset -d:ssl -r tests/mariadb/test_query.nim
import std/unittest
import std/asyncdispatch
import std/httpclient
import std/json
import std/options
import std/streams
import std/strformat
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = mariadb
proc setup(rdb:MariadbConnections) =
rdb.create([
table("auth",[
Column.increments("id"),
Column.string("auth")
]),
table("user",[
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.date("submit_on").nullable(),
Column.datetime("submit_at").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL).nullable()
])
])
# seeder
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
])
.waitFor
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
let month = if i > 9: $i else: &"0{i}"
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId,
"submit_on": &"2020-{month}-01",
"submit_at": &"2020-{month}-01 00:00:00",
}
)
rdb.table("user").insert(users).waitFor
setup(rdb)
suite($rdb & " get"):
test("get"):
let t = rdb.table("user").get().waitFor
let expect = %*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1}
echo t[0]
echo expect
check t[0] == expect
test("getPlain"):
let t = rdb.table("user").getPlain().waitFor
check t[0] == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("first"):
var t = rdb.table("user").where("name", "=", "user1").first().waitFor().get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("firstPlain"):
var t = rdb.table("user").firstPlain().waitFor
check t == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("find"):
var t = rdb.table("user").find(1).waitFor.get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("findString"):
var t = rdb.table("user").find("1").waitFor.get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("findPlain"):
var t = rdb.table("user").findPlain(1).waitFor
check t == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("columns"):
let columns = rdb.table("user").columns().waitFor
check columns == @["id", "name", "email", "address", "submit_on", "submit_at", "auth_id"]
test("select"):
var t = rdb.select("name", "email").table("user").get().waitFor
check t[0] == %*{"name": "user1", "email": "user1@example.com"}
test("selectAs"):
var t = rdb.select("name as user_name", "email").table("user").get().waitFor
check t[0] == %*{"user_name": "user1", "email": "user1@example.com"}
test("selectLike"):
var t = rdb.select("email").table("user").where("email", "LIKE", "%10%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
t = rdb.select("email").table("user").where("email", "LIKE", "user10%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
t = rdb.select("email").table("user").where("email", "LIKE", "%10@example.com%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
test("select count"):
var t = rdb.select("COUNT(id) as count").table("user").get().waitFor
check t[0]["count"].getInt() == 10
test("select max"):
var t = rdb.select("MAX(id) as max").table("user").get().waitFor
check t[0]["max"].getInt() == 10
test("where"):
var t = rdb.table("user").where("auth_id", "=", "1").get().waitFor
check t == @[
%*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1},
%*{"id":3,"name":"user3","email":"user3@example.com","address":newJNull(),"submit_on":"2020-03-01","submit_at":"2020-03-01 00:00:00","auth_id":1},
%*{"id":5,"name":"user5","email":"user5@example.com","address":newJNull(),"submit_on":"2020-05-01","submit_at":"2020-05-01 00:00:00","auth_id":1},
%*{"id":7,"name":"user7","email":"user7@example.com","address":newJNull(),"submit_on":"2020-07-01","submit_at":"2020-07-01 00:00:00","auth_id":1},
%*{"id":9,"name":"user9","email":"user9@example.com","address":newJNull(),"submit_on":"2020-09-01","submit_at":"2020-09-01 00:00:00","auth_id":1}
]
test("orWhere"):
var t = rdb.table("user").where("auth_id", "=", "1").orWhere("name", "=", "user2").get().waitFor
check t == @[
%*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1},
%*{"id":2,"name":"user2","email":"user2@example.com","address":newJNull(),"submit_on":"2020-02-01","submit_at":"2020-02-01 00:00:00","auth_id":2},
%*{"id":3,"name":"user3","email":"user3@example.com","address":newJNull(),"submit_on":"2020-03-01","submit_at":"2020-03-01 00:00:00","auth_id":1},
%*{"id":5,"name":"user5","email":"user5@example.com","address":newJNull(),"submit_on":"2020-05-01","submit_at":"2020-05-01 00:00:00","auth_id":1},
%*{"id":7,"name":"user7","email":"user7@example.com","address":newJNull(),"submit_on":"2020-07-01","submit_at":"2020-07-01 00:00:00","auth_id":1},
%*{"id":9,"name":"user9","email":"user9@example.com","address":newJNull(),"submit_on":"2020-09-01","submit_at":"2020-09-01 00:00:00","auth_id":1}
]
test("whereBetween"):
var t = rdb
.select("id", "name")
.table("user")
.where("auth_id", "=", 1)
.whereBetween("id", [6, 9])
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user7"
test("whereBetweenString"):
var t = rdb
.table("user")
.whereBetween("submit_at", ["2020-01-01 00:00:00", "2020-01-31 00:00:00"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
t = rdb
.table("user")
.whereBetween("submit_on", ["2020-01-01", "2020-01-31"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
test("whereNotBetween"):
var t = rdb
.select("id", "name")
.table("user")
.where("auth_id", "=", 1)
.whereNotBetween("id", [1, 4])
.get()
.waitFor
check t[0]["name"].getStr() == "user5"
test("whereNotBetweenString"):
var t = rdb
.table("user")
.whereNotBetween("submit_at", ["2020-2-1 00:00:00", "2020-12-31 00:00:00"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
t = rdb
.table("user")
.whereNotBetween("submit_on", ["2020-2-1", "2020-12-31"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
test("whereIn"):
var t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereIn("id", @[5, 6, 7])
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user5"
t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereIn("name", @["user5", "user6", "user7"])
.get()
.waitFor
check t[0]["name"].getStr() == "user5"
test("whereNotIn"):
var t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereNotIn("id", @[5, 6, 7])
.get()
.waitFor
echo t
check t == @[
%*{"id":4, "name": "user4"},
%*{"id":8, "name": "user8"},
%*{"id":9, "name": "user9"},
%*{"id":10, "name": "user10"},
]
t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereNotIn("name", @["user5", "user6", "user7"])
.get()
.waitFor
echo t
check t == @[
%*{"id":4, "name": "user4"},
%*{"id":8, "name": "user8"},
%*{"id":9, "name": "user9"},
%*{"id":10, "name": "user10"},
]
test("whereNull"):
setUp(rdb)
rdb.table("user").insert(%*{"email": "user11@example.com"}).waitFor
var t = rdb
.select("id", "name", "email")
.table("user")
.whereNull("name")
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 11
test("groupBy"):
var t = rdb
.table("user")
.groupBy("auth_id")
.max("id")
.waitFor()
.get()
echo t
check t == "11"
test("having"):
var t = rdb
.select("id", "name")
.table("user")
.groupBy("auth_id")
.groupBy("id")
.having("auth_id", "=", 1)
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 1
test("orderBy"):
var t = rdb.table("user")
.where("auth_id", "is not", nil)
.orderBy("auth_id", Asc)
.orderBy("id", Desc)
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 9
test("join"):
var t = rdb
.select("user.id", "user.name")
.table("user")
.join("auth", "auth.id", "=", "user.auth_id")
.where("auth.id", "=", "2")
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user2"
test("leftJoin"):
rdb.table("user").insert(%*{
"name": "user11"
})
.waitFor
var t = rdb
.select("user.id", "user.name", "user.auth_id")
.table("user")
.leftJoin("auth", "auth.id", "=", "user.auth_id")
.orderBy("user.id", Desc)
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user11"
check t[0]["auth_id"] == newJNull()
test("resultIsNull"):
check rdb.table("user").find(50).waitFor().isSome == false
check newSeq[JsonNode](0) == rdb.table("user").where("id", "=", 50).get().waitFor
suite($rdb & " insert"):
setup:
setup(rdb)
test("insert row"):
rdb.table("user").insert(%*{"name": "Alice"}).waitFor
let t = rdb.table("user").orderBy("id", Desc).first().waitFor().get()
check t["name"].getStr() == "Alice"
test("insert rows"):
rdb.table("user").insert(@[
%*{"name": "Alice"},
%*{"name": "Bob"},
]).waitFor
let t = rdb.table("user").orderBy("id", Desc).limit(2).get().waitFor()
check t[0]["name"].getStr() == "Bob"
check t[1]["name"].getStr() == "Alice"
test("insertId row"):
var id = rdb.table("user").insertId(%*{"name": "Alice"}).waitFor
echo id
let t = rdb.table("user").find(id).waitFor().get()
check t["name"].getStr() == "Alice"
test("insertId rows"):
let ids = rdb.table("user").insertId(@[
%*{"name": "Alice"},
%*{"name": "Bob"},
]).waitFor
var t = rdb.table("user").find(ids[0]).waitFor().get()
check t["name"].getStr == "Alice"
t = rdb.table("user").find(ids[1]).waitFor().get()
check t["name"].getStr == "Bob"
test("insertNil"):
var id = rdb.table("user").insertId(%*{
"name": "Alice",
"email": nil,
"address": ""
})
.waitFor
var res = rdb.table("user").find(id).waitFor
echo res.get
check res.get["email"] == newJNull()
res = rdb.table("user").where("email", "is", nil).first().waitFor
echo res.get
check res.get["email"] == newJNull()
test("insert [T]"):
type User = object
name:string
let user = User(name: "Alice")
rdb.table("user").insert(user).waitFor
let row = rdb.table("user").orderBy("id", Desc).first().waitFor().get()
check row["name"].getStr() == "Alice"
test("insert seq[T]"):
type User = object
name:string
let users = @[
User(name: "Alice"),
User(name: "Bob"),
]
rdb.table("user").insert(users).waitFor
let t = rdb.table("user").orderBy("id", Desc).limit(2).get().waitFor()
check t[0]["name"].getStr() == "Bob"
check t[1]["name"].getStr() == "Alice"
suite($rdb & " update"):
setup:
setup(rdb)
test("update"):
rdb.table("user").where("id", "=", 1).update(%*{"name": "Alice"}).waitFor
var t = rdb.table("user").find(1).waitFor().get()
check t["name"].getStr() == "Alice"
test("update [T]"):
type User = object
name:string
let user = User(name: "Alice")
rdb.table("user").where("id", "=", 1).update(user).waitFor
var t = rdb.table("user").find(1).waitFor().get()
check t["name"].getStr() == "Alice"
suite($rdb & " delete"):
setup(rdb)
test("delete"):
rdb.table("user").delete(1).waitFor
check rdb.table("user").find(1).waitFor.isSome == false
test("delete where"):
rdb.table("user").where("name", "=", "user1").delete().waitFor
check rdb.table("user").find(1).waitFor.isSome == false
suite($rdb & " rawQuery"):
setup(rdb)
test("get"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT * FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %*[1]).get().waitFor
echo res
check res[0]["name"].getStr == "user1"
test("getPlain"):
let sql = &"SELECT * FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %*[1]).getPlain().waitFor
echo res
check res[0][1] == "user1"
test("first"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT * FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %*[1]).first().waitFor().get()
echo res
check res["name"].getStr == "user1"
test("firstPlain"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT name FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %*[1]).firstPlain().waitFor()
echo res
check res[0] == "user1"
test("exec"):
var sql = "UPDATE `user` SET `name` = ? WHERE `id` = ?"
rdb.raw(sql, %*["updated user1", 1]).exec().waitFor
sql = &"SELECT * FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %[1]).get().waitFor
echo res
check res[0]["name"].getStr == "updated user1"
setup(rdb)
suite($rdb & " aggregates"):
test("count"):
var t = rdb.table("user").count().waitFor
check t == 10
test("max"):
var t = rdb.table("user").max("name").waitFor.get
check t == "user9"
var t2 = rdb.table("user").max("id").waitFor.get
check t2 == "10"
test("min"):
var t = rdb.table("user").min("name").waitFor.get
check t == "user1"
var t2 = rdb.table("user").min("id").waitFor.get
check t2 == "1"
test("avg"):
var t = rdb.table("user").avg("id").waitFor.get
check t == 5.5
test("sum"):
var t = rdb.table("user").sum("id").waitFor.get
check t == 55.0
suite($rdb & " insert binary"):
test("should success"):
rdb.create(
table("test", [
Column.increments("id"),
Column.binary("pic"),
])
)
let client = newAsyncHttpClient()
let response = client.getContent("https://nim-lang.org/assets/img/twitter_banner.png").waitFor()
let imageStream = newStringStream(response)
let binaryImage = imageStream.readAll()
let id = rdb.table("test").insertId(%*{"pic": binaryImage}).waitFor()
var res = rdb.table("test").find(id).waitFor().get()
check res["pic"].getStr().len > 0
rdb.table("test").where("id","=", id).update(%*{"pic": binaryImage}).waitFor()
res = rdb.table("test").find(id).waitFor().get()
check res["pic"].getStr().len > 0
clearTables(rdb).waitFor()
@@ -0,0 +1,308 @@
discard """
cmd: "nim c -d:reset $file"
"""
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = mariadb
suite("MariaDB create table"):
test("create table"):
rdb.create(
table("IntRelation", [
Column.increments("id")
]),
table("StrRelation", [
Column.uuid("uuid")
]),
table("TypeIndex", [
Column.increments("id"),
Column.integer("integer").unsigned().index().default(1),
Column.smallInteger("smallInteger").unsigned().index().default(1),
Column.mediumInteger("mediumInteger").unsigned().index().default(1),
Column.bigInteger("bigInteger").unsigned().index().default(1),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1),
Column.double("double", 10, 3).unsigned().index().default(1.1),
Column.float("float").unsigned().index().default(1.1),
Column.uuid("uuid").index().default("A"),
Column.char("char", 255).index().default("A"),
Column.string("string").index().default("A"),
Column.text("text").index().default("A"),
Column.mediumText("mediumText").index().default("A"),
Column.longText("longText").index().default("A"),
Column.date("date").index().default(),
Column.datetime("datetime").index().default(),
Column.time("time").index().default(),
Column.timestamp("timestamp").index().default(),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary").index().default("A"),
Column.boolean("boolean").index().default(true),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A"),
Column.json("json").index().default(%*{"key":"value"}),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL)
]),
table("TypeUnique", [
Column.increments("id"),
Column.integer("integer").unsigned().unique().index().default(1),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1),
Column.float("float").unsigned().unique().index().default(1.1),
Column.uuid("uuid").unique().index().default("A"),
Column.char("char", 255).unique().index().default("A"),
Column.string("string").unique().index().default("A"),
Column.text("text").unique().index().default("A"),
Column.mediumText("mediumText").unique().index().default("A"),
Column.longText("longText").unique().index().default("A"),
Column.date("date").unique().index().default(),
Column.datetime("datetime").unique().index().default(),
Column.time("time").unique().index().default(),
Column.timestamp("timestamp").unique().index().default(),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary").unique().index().default("A"),
Column.boolean("boolean").unique().index().default(true),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A"),
Column.json("json").unique().index().default(%*{"key":"value"}),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL)
])
)
test("change columns"):
rdb.alter(
table("TypeIndex", [
Column.integer("integer").unsigned().index().default(1).change(),
Column.smallInteger("smallInteger").unsigned().index().default(1).change(),
Column.mediumInteger("mediumInteger").unsigned().index().default(1).change(),
Column.bigInteger("bigInteger").unsigned().index().default(1).change(),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1).change(),
Column.double("double", 10, 3).unsigned().index().default(1.1).change(),
Column.float("float").unsigned().index().default(1.1).change(),
Column.uuid("uuid").index().default("A").change(),
Column.char("char", 255).index().default("A").change(),
Column.string("string").index().default("A").change(),
Column.text("text").index().default("A").change(),
Column.mediumText("mediumText").index().default("A").change(),
Column.longText("longText").index().default("A").change(),
Column.date("date").index().default().change(),
Column.datetime("datetime").index().default().change(),
Column.time("time").index().default().change(),
Column.timestamp("timestamp").index().default().change(),
Column.binary("binary").index().default("A").change(),
Column.boolean("boolean").index().default(true).change(),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A").change(),
Column.json("json").index().default(%*{"key":"value"}).change(),
]),
table("TypeUnique", [
Column.integer("integer").unsigned().unique().index().default(1).change(),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1).change(),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1).change(),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1).change(),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1).change(),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1).change(),
Column.float("float").unsigned().unique().index().default(1.1).change(),
Column.uuid("uuid").unique().index().default("A").change(),
Column.char("char", 255).unique().index().default("A").change(),
Column.string("string").unique().index().default("A").change(),
Column.text("text").unique().index().default("A").change(),
Column.mediumText("mediumText").unique().index().default("A").change(),
Column.longText("longText").unique().index().default("A").change(),
Column.date("date").unique().index().default().change(),
Column.datetime("datetime").unique().index().default().change(),
Column.time("time").unique().index().default().change(),
Column.timestamp("timestamp").unique().index().default().change(),
Column.binary("binary").unique().index().default("A").change(),
Column.boolean("boolean").unique().index().default(true).change(),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A").change(),
Column.json("json").unique().index().default(%*{"key":"value"}).change(),
])
)
suite($rdb & " alter table"):
setup:
rdb.create(
table("IntRelation", [
Column.increments("id"),
]),
table("StrRelation", [
Column.uuid("uuid"),
]),
table("TypeUnique", [
Column.integer("num"),
Column.string("str"),
]),
table("TypeIndex", [
Column.integer("num"),
Column.string("str"),
])
)
test("add columns"):
rdb.alter(
table("TypeIndex", [
Column.increments("id").add(),
Column.integer("integer").unsigned().index().default(1).add(),
Column.smallInteger("smallInteger").unsigned().index().default(1).add(),
Column.mediumInteger("mediumInteger").unsigned().index().default(1).add(),
Column.bigInteger("bigInteger").unsigned().index().default(1).add(),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1).add(),
Column.double("double", 10, 3).unsigned().index().default(1.1).add(),
Column.float("float").unsigned().index().default(1.1).add(),
Column.uuid("uuid").index().default("A").add(),
Column.char("char", 255).index().default("A").add(),
Column.string("string").index().default("A").add(),
Column.text("text").index().default("A").add(),
Column.mediumText("mediumText").index().default("A").add(),
Column.longText("longText").index().default("A").add(),
Column.date("date").index().default().add(),
Column.datetime("datetime").index().default(),
Column.time("time").index().default().add(),
Column.timestamp("timestamp").index().default().add(),
Column.timestamps().add(),
Column.softDelete().add(),
Column.binary("binary").index().default("A").add(),
Column.boolean("boolean").index().default(true).add(),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A").add(),
Column.json("json").index().default(%*{"key":"value"}).add(),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL).add(),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL).add()
]),
table("TypeUnique", [
Column.increments("id").add(),
Column.integer("integer").unsigned().unique().index().default(1).add(),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1).add(),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1).add(),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1).add(),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1).add(),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1).add(),
Column.float("float").unsigned().unique().index().default(1.1).add(),
Column.uuid("uuid").unique().index().default("A").add(),
Column.char("char", 255).unique().index().default("A").add(),
Column.string("string").unique().index().default("A").add(),
Column.text("text").unique().index().default("A").add(),
Column.mediumText("mediumText").unique().index().default("A").add(),
Column.longText("longText").unique().index().default("A").add(),
Column.date("date").unique().index().default().add(),
Column.datetime("datetime").unique().index().default().add(),
Column.time("time").unique().index().default().add(),
Column.timestamp("timestamp").unique().index().default().add(),
Column.timestamps().add(),
Column.softDelete().add(),
Column.binary("binary").unique().index().default("A").add(),
Column.boolean("boolean").unique().index().default(true).add(),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A").add(),
Column.json("json").unique().index().default(%*{"key":"value"}).add()
])
)
test("rename column"):
rdb.alter(
table("TypeIndex", [
Column.renameColumn("num", "num2"),
Column.renameColumn("str", "str2"),
])
)
let columns = rdb.table("TypeIndex").columns().waitFor
check not columns.contains("num")
check columns.contains("num2")
check not columns.contains("str")
check columns.contains("str2")
test("drop column"):
rdb.alter(
table("TypeIndex", [
Column.dropColumn("str")
])
)
let columns = rdb.table("TypeIndex").columns().waitFor
check not columns.contains("str")
test("rename table"):
rdb.drop(
table("TypeIndex_renamed")
)
rdb.table("TypeIndex").insert(%*{"num":1, "str": "a"}).waitFor
rdb.alter(
table("TypeIndex").renameTo("TypeIndex_renamed")
)
var res:Option[JsonNode]
try:
res = rdb.table("TypeIndex").first().waitFor
check not res.isSome
except CatchableError:
check true
res = rdb.table("TypeIndex_renamed").first().waitFor
check res.isSome
test("drop table"):
rdb.create(
table("TypeIndex", [
Column.integer("num")
])
)
rdb.drop(
table("TypeIndex")
)
var res:Option[JsonNode]
try:
res = rdb.table("TypeIndex").first().waitFor
check not res.isSome
except CatchableError:
check true
suite($rdb & " primary"):
test("primary key"):
rdb.create(
table("relation", [
Column.increments("id"),
]),
table("TypeIndex", @[
Column.integer("index1"),
Column.integer("index2"),
Column.string("string"),
Column.foreign("relation_id").reference("id").onTable("relation").onDelete(SET_NULL)
],
primary = @["index1", "index2"]
)
)
suite("Comment"):
test("column comment"):
rdb.create(
table("Comment", [
Column.integer("index1").comment("index1 comment"),
Column.string("string").comment("string comment")
])
)
clearTables(rdb).waitFor()
@@ -0,0 +1,128 @@
discard """
cmd: "nim c --mm:orc -d:reset -r $file"
"""
import std/unittest
import std/json
import std/strformat
import std/strutils
import std/options
import std/asyncdispatch
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = mariadb
proc setUp(rdb:MariadbConnections) =
rdb.create([
table("auth",[
Column.increments("id"),
Column.string("auth")
]),
table("user",[
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL)
])
])
# seeder
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
])
.waitFor()
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId
}
)
rdb.table("user").insert(users).waitFor()
suite("raw code transaction"):
setup:
setUp(rdb)
test("commit"):
(proc() {.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
block:
rdb.begin().await
try:
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
rdb.commit().await
except CatchableError:
rdb.rollback().await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "updated"
)().waitFor
test("rollback"):
(proc() {.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
block:
rdb.begin().await
try:
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
raise newException(CatchableError, "")
except:
rdb.rollback().await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
)().waitFor
suite("transaction template"):
setup:
setUp(rdb)
test("commit"):
(proc(){.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
transaction(rdb):
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "updated"
)().waitFor()
test("rollback"):
(proc(){.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
transaction(rdb):
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
raise newException(DbError, "")
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
)().waitFor()
clearTables(rdb).waitFor()
@@ -0,0 +1,10 @@
import std/os
import std/strutils
import ../../src/allographer/connection
let
mysqlUrl = getEnv("MY_URL")
maxConnections = getEnv("DB_MAX_CONNECTION").parseInt
timeout = getEnv("DB_TIMEOUT").parseInt
let mysql* = dbOpen(MySQL, databaseUrl = asDatabaseUrl(mysqlUrl), maxConnections, timeout, shouldDisplayLog=true)
@@ -0,0 +1,108 @@
discard """
cmd: "nim c -d:reset $file"
"""
# nim c -d:reset -r tests/mysql/test_create_schema.nim
import std/unittest
import std/asyncdispatch
import std/os
import std/strutils
import std/json
import ../../src/allographer/schema_builder
import ./connections
import ../clear_tables
let rdb = mysql
let schemaFilePath = getCurrentDir() / "schema.nim"
clearTables(rdb).waitFor()
suite "Schema output after migration":
setup:
# schema.nim ファイルの削除
if fileExists(schemaFilePath):
removeFile(schemaFilePath)
rdb.create(
table("int_relation", [
Column.increments("id")
]),
table("str_relation", [
Column.uuid("uuid")
]),
table("test_schema_output", [
Column.increments("id"),
Column.integer("integer"),
Column.smallInteger("smallInteger"),
Column.mediumInteger("mediumInteger"),
Column.bigInteger("bigInteger"),
Column.decimal("decimal", 10, 3),
Column.double("double", 10, 3),
Column.float("float"),
Column.uuid("uuid"),
Column.char("char", 255),
Column.string("string"),
Column.text("text"),
Column.mediumText("mediumText"),
Column.longText("longText"),
Column.date("date"),
Column.datetime("datetime"),
Column.time("time"),
Column.timestamp("timestamp"),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary"),
Column.boolean("boolean"),
Column.enumField("enumField", ["A", "B", "C"]),
Column.json("json"),
Column.foreign("int_relation_id").reference("id").onTable("int_relation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("str_relation").onDelete(SET_NULL)
])
)
test "should generate schema.nim file":
# スキーマ生成
rdb.createSchema(schemaFilePath).waitFor()
# schema.nim ファイルの存在確認
check fileExists(schemaFilePath)
# ファイル内容の検証
let schemaContent = readFile(schemaFilePath)
echo "schemaContent: ", schemaContent
check schemaContent.contains("type TestSchemaOutputTable* = object")
check schemaContent.contains("## test_schema_output")
check schemaContent.contains("integer*: int")
check schemaContent.contains("smallInteger*: int")
check schemaContent.contains("mediumInteger*: int")
check schemaContent.contains("bigInteger*: int")
check schemaContent.contains("decimal*: float")
check schemaContent.contains("double*: float")
check schemaContent.contains("float*: float")
check schemaContent.contains("uuid*: string")
check schemaContent.contains("char*: string")
check schemaContent.contains("string*: string")
check schemaContent.contains("text*: string")
check schemaContent.contains("mediumText*: string")
check schemaContent.contains("longText*: string")
check schemaContent.contains("date*: string")
check schemaContent.contains("datetime*: string")
check schemaContent.contains("time*: string")
check schemaContent.contains("timestamp*: string")
check schemaContent.contains("created_at*: string")
check schemaContent.contains("updated_at*: string")
check schemaContent.contains("deleted_at*: string")
check schemaContent.contains("binary*: string")
check schemaContent.contains("boolean*: bool")
check schemaContent.contains("enumField*: string")
check schemaContent.contains("json*: JsonNode")
check schemaContent.contains("int_relation_id*: int")
check schemaContent.contains("str_relation_id*: string")
clearTables(rdb).waitFor()
# schema.nim ファイルの削除
if fileExists(schemaFilePath):
removeFile(schemaFilePath)
@@ -0,0 +1,45 @@
discard """
cmd: "nim c $file"
"""
# nim c -r tests/v2/mariadb/test_open.nim
import std/unittest
import ../../src/allographer/connection
import ../../src/allographer/query_builder
suite("MySQL connection"):
test("connection"):
let database = "database"
let user = "user"
let password = "pass"
let host = "mariadb"
let port = 3306
let rdb = dbOpen(MySQL, database, user, password, host, port)
check(rdb.isConnected())
test("connection with URL"):
let url = "mysql://user:pass@mariadb:3306/database"
let rdb = dbOpen(MySQL, url)
check(rdb.isConnected())
test("connection with DatabaseUrl"):
let url = asDatabaseUrl("mysql://user:pass@mariadb:3306/database")
let rdb = dbOpen(MySQL, databaseUrl = url)
check(rdb.isConnected())
test("url is not start with mysql://"):
let url = "aaa://user:pass@mariadb:3306/database"
expect(ValueError):
discard dbOpen(MySQL, url)
test("cannot reach to database"):
let url = "mysql://john:password@host:0001/database"
expect(DbError):
discard dbOpen(MySQL, url)
@@ -0,0 +1,149 @@
discard """
cmd: "nim c -d:reset -d:ssl -r $file"
"""
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import std/strformat
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
let rdb = mysql
proc setup(rdb: MysqlConnections) =
rdb.create([
table("auth", [
Column.increments("id"),
Column.string("auth")
]),
table("user", [
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.date("submit_on").nullable(),
Column.datetime("submit_at").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL).nullable()
])
])
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
]).waitFor
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
let month = if i > 9: $i else: &"0{i}"
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId,
"submit_on": &"2020-{month}-01",
"submit_at": &"2020-{month}-01 00:00:00",
}
)
rdb.table("user").insert(users).waitFor
setup(rdb)
suite($rdb & " prepared statement"):
test("aged connection refresh"):
rdb.pools.maxConnectionLifetime = 1
rdb.pools.maxConnectionIdleTime = 1
for conn in rdb.pools.conns.mitems:
conn.createdAt = 0
conn.lastUsedAt = 0
let sql = """SELECT `id`, `name`, `email`, `address` FROM `user` WHERE `id` = ?"""
let stmt = rdb.prepare(sql)
defer:
waitFor stmt.close()
let rowOpt = stmt.first(@["1"]).waitFor
check rowOpt.isSome
var refreshed = false
for conn in rdb.pools.conns:
if conn.createdAt > 0:
refreshed = true
break
check refreshed
test("select"):
let stmt = rdb.prepare("""SELECT `id`, `name`, `email`, `address` FROM `user` WHERE `id` = ?""")
defer:
waitFor stmt.close()
let args = newJArray()
args.add(newJInt(1))
let rows = stmt.get(args).waitFor
check rows.len == 1
check rows[0] == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address": newJNull()}
let rowOpt = stmt.first(args).waitFor
let row = options.get(rowOpt)
check row["name"].getStr == "user1"
check stmt.getPlain(args).waitFor[0][1] == "user1"
check stmt.firstPlain(args).waitFor[1] == "user1"
test("logical close and clear cache"):
let sql = """SELECT `id`, `name`, `email`, `address` FROM `user` WHERE `id` = ?"""
let stmt = rdb.prepare(sql)
discard waitFor stmt.first(@["1"])
waitFor stmt.close()
let stmt2 = rdb.prepare(sql)
defer:
waitFor stmt2.close()
let rowOpt = stmt2.first(@["1"]).waitFor
check rowOpt.isSome
waitFor rdb.clearStmtCache()
let stmt3 = rdb.prepare(sql)
defer:
waitFor stmt3.close()
check stmt3.first(@["1"]).waitFor.isSome
test("with prepared connection context"):
let selectStmt = rdb.prepare("""SELECT `id`, `name` FROM `user` WHERE `id` = ?""")
let updateStmt = rdb.prepare("""UPDATE `user` SET `address` = ? WHERE `id` = ?""")
defer:
waitFor selectStmt.close()
waitFor updateStmt.close()
waitFor rdb.withConn(
proc(ctx: MysqlPreparedContext): Future[void] {.async.} =
discard await selectStmt.first(ctx, @["1"])
await updateStmt.exec(ctx, @["ctx-address", "1"])
)
let rowOpt = rdb.table("user").find(1).waitFor
let row = options.get(rowOpt)
check row["address"].getStr == "ctx-address"
test("update null"):
let stmt = rdb.prepare("""UPDATE `user` SET `address` = ? WHERE `id` = ?""")
defer:
waitFor stmt.close()
waitFor stmt.exec(@["NULL", "1"])
let rowOpt = rdb.table("user").find(1).waitFor
let row = options.get(rowOpt)
check row["address"].kind == JNull
@@ -0,0 +1,565 @@
discard """
cmd: "nim c -d:reset -d:ssl -r $file"
"""
# nim c -d:reset -d:ssl -r tests/mysql/test_query.nim
import std/unittest
import std/asyncdispatch
import std/httpclient
import std/json
import std/options
import std/streams
import std/strformat
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = mysql
proc setup(rdb:MysqlConnections) =
rdb.create([
table("auth",[
Column.increments("id"),
Column.string("auth")
]),
table("user",[
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.date("submit_on").nullable(),
Column.datetime("submit_at").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL).nullable()
])
])
# seeder
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
])
.waitFor
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
let month = if i > 9: $i else: &"0{i}"
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId,
"submit_on": &"2020-{month}-01",
"submit_at": &"2020-{month}-01 00:00:00",
}
)
rdb.table("user").insert(users).waitFor
setup(rdb)
suite($rdb & " get"):
test("get"):
let t = rdb.table("user").get().waitFor
let expect = %*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1}
echo t[0]
echo expect
check t[0] == expect
test("getPlain"):
let t = rdb.table("user").getPlain().waitFor
check t[0] == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("first"):
var t = rdb.table("user").where("name", "=", "user1").first().waitFor().get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("firstPlain"):
var t = rdb.table("user").firstPlain().waitFor
check t == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("find"):
var t = rdb.table("user").find(1).waitFor.get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("findString"):
var t = rdb.table("user").find("1").waitFor.get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("findPlain"):
var t = rdb.table("user").findPlain(1).waitFor
check t == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("columns"):
let columns = rdb.table("user").columns().waitFor
check columns == @["id", "name", "email", "address", "submit_on", "submit_at", "auth_id"]
test("select"):
var t = rdb.select("name", "email").table("user").get().waitFor
check t[0] == %*{"name": "user1", "email": "user1@example.com"}
test("selectAs"):
var t = rdb.select("name as user_name", "email").table("user").get().waitFor
check t[0] == %*{"user_name": "user1", "email": "user1@example.com"}
test("selectLike"):
var t = rdb.select("email").table("user").where("email", "LIKE", "%10%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
t = rdb.select("email").table("user").where("email", "LIKE", "user10%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
t = rdb.select("email").table("user").where("email", "LIKE", "%10@example.com%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
test("select count"):
var t = rdb.select("COUNT(id) as count").table("user").get().waitFor
check t[0]["count"].getInt() == 10
test("select max"):
var t = rdb.select("MAX(id) as max").table("user").get().waitFor
check t[0]["max"].getInt() == 10
test("where"):
var t = rdb.table("user").where("auth_id", "=", "1").get().waitFor
check t == @[
%*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1},
%*{"id":3,"name":"user3","email":"user3@example.com","address":newJNull(),"submit_on":"2020-03-01","submit_at":"2020-03-01 00:00:00","auth_id":1},
%*{"id":5,"name":"user5","email":"user5@example.com","address":newJNull(),"submit_on":"2020-05-01","submit_at":"2020-05-01 00:00:00","auth_id":1},
%*{"id":7,"name":"user7","email":"user7@example.com","address":newJNull(),"submit_on":"2020-07-01","submit_at":"2020-07-01 00:00:00","auth_id":1},
%*{"id":9,"name":"user9","email":"user9@example.com","address":newJNull(),"submit_on":"2020-09-01","submit_at":"2020-09-01 00:00:00","auth_id":1}
]
test("orWhere"):
var t = rdb.table("user").where("auth_id", "=", "1").orWhere("name", "=", "user2").get().waitFor
check t == @[
%*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1},
%*{"id":2,"name":"user2","email":"user2@example.com","address":newJNull(),"submit_on":"2020-02-01","submit_at":"2020-02-01 00:00:00","auth_id":2},
%*{"id":3,"name":"user3","email":"user3@example.com","address":newJNull(),"submit_on":"2020-03-01","submit_at":"2020-03-01 00:00:00","auth_id":1},
%*{"id":5,"name":"user5","email":"user5@example.com","address":newJNull(),"submit_on":"2020-05-01","submit_at":"2020-05-01 00:00:00","auth_id":1},
%*{"id":7,"name":"user7","email":"user7@example.com","address":newJNull(),"submit_on":"2020-07-01","submit_at":"2020-07-01 00:00:00","auth_id":1},
%*{"id":9,"name":"user9","email":"user9@example.com","address":newJNull(),"submit_on":"2020-09-01","submit_at":"2020-09-01 00:00:00","auth_id":1}
]
test("whereBetween"):
var t = rdb
.select("id", "name")
.table("user")
.where("auth_id", "=", 1)
.whereBetween("id", [6, 9])
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user7"
test("whereBetweenString"):
var t = rdb
.table("user")
.whereBetween("submit_at", ["2020-01-01 00:00:00", "2020-01-31 00:00:00"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
t = rdb
.table("user")
.whereBetween("submit_on", ["2020-01-01", "2020-01-31"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
test("whereNotBetween"):
var t = rdb
.select("id", "name")
.table("user")
.where("auth_id", "=", 1)
.whereNotBetween("id", [1, 4])
.get()
.waitFor
check t[0]["name"].getStr() == "user5"
test("whereNotBetweenString"):
var t = rdb
.table("user")
.whereNotBetween("submit_at", ["2020-2-1 00:00:00", "2020-12-31 00:00:00"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
t = rdb
.table("user")
.whereNotBetween("submit_on", ["2020-2-1", "2020-12-31"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
test("whereIn"):
var t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereIn("id", @[5, 6, 7])
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user5"
t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereIn("name", @["user5", "user6", "user7"])
.get()
.waitFor
check t[0]["name"].getStr() == "user5"
test("whereNotIn"):
var t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereNotIn("id", @[5, 6, 7])
.get()
.waitFor
echo t
check t == @[
%*{"id":4, "name": "user4"},
%*{"id":8, "name": "user8"},
%*{"id":9, "name": "user9"},
%*{"id":10, "name": "user10"},
]
t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereNotIn("name", @["user5", "user6", "user7"])
.get()
.waitFor
echo t
check t == @[
%*{"id":4, "name": "user4"},
%*{"id":8, "name": "user8"},
%*{"id":9, "name": "user9"},
%*{"id":10, "name": "user10"},
]
test("whereNull"):
setUp(rdb)
rdb.table("user").insert(%*{"email": "user11@example.com"}).waitFor
var t = rdb
.select("id", "name", "email")
.table("user")
.whereNull("name")
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 11
test("groupBy"):
var t = rdb
.table("user")
.where("auth_id", "=", 1)
.groupBy("auth_id")
.max("id")
.waitFor()
.get()
echo t
check t == "9"
test("having"):
var t = rdb
.select("id", "name")
.table("user")
.groupBy("auth_id")
.groupBy("id")
.having("auth_id", "=", 1)
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 1
test("orderBy"):
var t = rdb.table("user")
.where("auth_id", "is not", nil)
.orderBy("auth_id", Asc)
.orderBy("id", Desc)
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 9
test("join"):
var t = rdb
.select("user.id", "user.name")
.table("user")
.join("auth", "auth.id", "=", "user.auth_id")
.where("auth.id", "=", "2")
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user2"
test("leftJoin"):
rdb.table("user").insert(%*{
"name": "user11"
})
.waitFor
var t = rdb
.select("user.id", "user.name", "user.auth_id")
.table("user")
.leftJoin("auth", "auth.id", "=", "user.auth_id")
.orderBy("user.id", Desc)
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user11"
check t[0]["auth_id"] == newJNull()
test("resultIsNull"):
check rdb.table("user").find(50).waitFor().isSome == false
check newSeq[JsonNode](0) == rdb.table("user").where("id", "=", 50).get().waitFor
suite($rdb & " insert"):
setup:
setup(rdb)
test("insert row"):
rdb.table("user").insert(%*{"name": "Alice"}).waitFor
let t = rdb.table("user").orderBy("id", Desc).first().waitFor().get()
check t["name"].getStr() == "Alice"
test("insert rows"):
rdb.table("user").insert(@[
%*{"name": "Alice"},
%*{"name": "Bob"},
]).waitFor
let t = rdb.table("user").orderBy("id", Desc).limit(2).get().waitFor()
check t[0]["name"].getStr() == "Bob"
check t[1]["name"].getStr() == "Alice"
test("insertId row"):
var id = rdb.table("user").insertId(%*{"name": "Alice"}).waitFor
echo id
let t = rdb.table("user").find(id).waitFor().get()
check t["name"].getStr() == "Alice"
test("insertId rows"):
let ids = rdb.table("user").insertId(@[
%*{"name": "Alice"},
%*{"name": "Bob"},
]).waitFor
var t = rdb.table("user").find(ids[0]).waitFor().get()
check t["name"].getStr == "Alice"
t = rdb.table("user").find(ids[1]).waitFor().get()
check t["name"].getStr == "Bob"
test("insertNil"):
var id = rdb.table("user").insertId(%*{
"name": "Alice",
"email": nil,
"address": ""
})
.waitFor
var res = rdb.table("user").find(id).waitFor
echo res.get
check res.get["email"] == newJNull()
res = rdb.table("user").where("email", "is", nil).first().waitFor
echo res.get
check res.get["email"] == newJNull()
test("insert [T]"):
type User = object
name:string
let user = User(name: "Alice")
rdb.table("user").insert(user).waitFor
let row = rdb.table("user").orderBy("id", Desc).first().waitFor().get()
check row["name"].getStr() == "Alice"
test("insert seq[T]"):
type User = object
name:string
let users = @[
User(name: "Alice"),
User(name: "Bob"),
]
rdb.table("user").insert(users).waitFor
let t = rdb.table("user").orderBy("id", Desc).limit(2).get().waitFor()
check t[0]["name"].getStr() == "Bob"
check t[1]["name"].getStr() == "Alice"
suite($rdb & " update"):
setup:
setup(rdb)
test("update"):
rdb.table("user").where("id", "=", 1).update(%*{"name": "Alice"}).waitFor
var t = rdb.table("user").find(1).waitFor().get()
check t["name"].getStr() == "Alice"
test("update [T]"):
type User = object
name:string
let user = User(name: "Alice")
rdb.table("user").where("id", "=", 1).update(user).waitFor
var t = rdb.table("user").find(1).waitFor().get()
check t["name"].getStr() == "Alice"
suite($rdb & " delete"):
setup(rdb)
test("delete"):
rdb.table("user").delete(1).waitFor
check rdb.table("user").find(1).waitFor.isSome == false
test("delete where"):
rdb.table("user").where("name", "=", "user1").delete().waitFor
check rdb.table("user").find(1).waitFor.isSome == false
suite($rdb & " rawQuery"):
setup(rdb)
test("get"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT * FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %*[1]).get().waitFor
echo res
check res[0]["name"].getStr == "user1"
test("getPlain"):
let sql = &"SELECT * FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %*[1]).getPlain().waitFor
echo res
check res[0][1] == "user1"
test("first"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT * FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %*[1]).first().waitFor().get()
echo res
check res["name"].getStr == "user1"
test("firstPlain"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT name FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %*[1]).firstPlain().waitFor()
echo res
check res[0] == "user1"
test("exec"):
var sql = "UPDATE `user` SET `name` = ? WHERE `id` = ?"
rdb.raw(sql, %*["updated user1", 1]).exec().waitFor
sql = &"SELECT * FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %[1]).get().waitFor
echo res
check res[0]["name"].getStr == "updated user1"
setup(rdb)
suite($rdb & " aggregates"):
test("count"):
var t = rdb.table("user").count().waitFor
check t == 10
test("max"):
var t = rdb.table("user").max("name").waitFor.get
check t == "user9"
var t2 = rdb.table("user").max("id").waitFor.get
check t2 == "10"
test("min"):
var t = rdb.table("user").min("name").waitFor.get
check t == "user1"
var t2 = rdb.table("user").min("id").waitFor.get
check t2 == "1"
test("avg"):
var t = rdb.table("user").avg("id").waitFor.get
check t == 5.5
test("sum"):
var t = rdb.table("user").sum("id").waitFor.get
check t == 55.0
suite($rdb & " insert binary"):
test("should success"):
rdb.create(
table("test", [
Column.increments("id"),
Column.binary("pic"),
])
)
let client = newAsyncHttpClient()
let response = client.getContent("https://nim-lang.org/assets/img/twitter_banner.png").waitFor()
let imageStream = newStringStream(response)
let binaryImage = imageStream.readAll()
let id = rdb.table("test").insertId(%*{"pic": binaryImage}).waitFor()
var res = rdb.table("test").find(id).waitFor().get()
check res["pic"].getStr().len > 0
rdb.table("test").where("id","=", id).update(%*{"pic": binaryImage}).waitFor()
res = rdb.table("test").find(id).waitFor().get()
check res["pic"].getStr().len > 0
clearTables(rdb).waitFor()
@@ -0,0 +1,617 @@
discard """
cmd: "nim c -d:reset $file"
"""
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = mysql
suite("MySQL create table"):
test("create table"):
rdb.create(
table("IntRelation", [
Column.increments("id")
]),
table("StrRelation", [
Column.uuid("uuid")
]),
# text, binary, json column can't use default value and index
table("TypeIndex", [
Column.increments("id"),
Column.integer("integer").unsigned().index().default(1),
Column.smallInteger("smallInteger").unsigned().index().default(1),
Column.mediumInteger("mediumInteger").unsigned().index().default(1),
Column.bigInteger("bigInteger").unsigned().index().default(1),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1),
Column.double("double", 10, 3).unsigned().index().default(1.1),
Column.float("float").unsigned().index().default(1.1),
Column.uuid("uuid").index().default("A"),
Column.char("char", 255).index().default("A"),
Column.string("string").index().default("A"),
Column.text("text"),
Column.mediumText("mediumText"),
Column.longText("longText"),
Column.date("date").index().default(),
Column.datetime("datetime").index().default(),
Column.time("time").index().default(),
Column.timestamp("timestamp").index().default(),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary"),
Column.boolean("boolean").index().default(true),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A"),
Column.json("json"),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL)
]),
# text, binary, json column can't use default value, index and unique
table("TypeUnique", [
Column.increments("id"),
Column.integer("integer").unsigned().unique().index().default(1),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1),
Column.float("float").unsigned().unique().index().default(1.1),
Column.uuid("uuid").unique().index().default("A"),
Column.char("char", 255).unique().index().default("A"),
Column.string("string").unique().index().default("A"),
Column.text("text"),
Column.mediumText("mediumText"),
Column.longText("longText"),
Column.date("date").unique().index().default(),
Column.datetime("datetime").unique().index().default(),
Column.time("time").unique().index().default(),
Column.timestamp("timestamp").unique().index().default(),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary"),
Column.boolean("boolean").unique().index().default(true),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A"),
Column.json("json"),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL)
])
)
test("change columns"):
rdb.alter(
table("TypeIndex", [
Column.integer("integer").unsigned().index().default(1).change(),
Column.smallInteger("smallInteger").unsigned().index().default(1).change(),
Column.mediumInteger("mediumInteger").unsigned().index().default(1).change(),
Column.bigInteger("bigInteger").unsigned().index().default(1).change(),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1).change(),
Column.double("double", 10, 3).unsigned().index().default(1.1).change(),
Column.float("float").unsigned().index().default(1.1).change(),
Column.uuid("uuid").index().default("A").change(),
Column.char("char", 255).index().default("A").change(),
Column.string("string").index().default("A").change(),
Column.text("text").change(),
Column.mediumText("mediumText").change(),
Column.longText("longText").change(),
Column.date("date").index().default().change(),
Column.datetime("datetime").index().default().change(),
Column.time("time").index().default().change(),
Column.timestamp("timestamp").index().default().change(),
Column.binary("binary").change(),
Column.boolean("boolean").index().default(true).change(),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A").change(),
Column.json("json").change(),
]),
table("TypeUnique", [
Column.integer("integer").unsigned().unique().index().default(1).change(),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1).change(),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1).change(),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1).change(),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1).change(),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1).change(),
Column.float("float").unsigned().unique().index().default(1.1).change(),
Column.uuid("uuid").unique().index().default("A").change(),
Column.char("char", 255).unique().index().default("A").change(),
Column.string("string").unique().index().default("A").change(),
Column.text("text").change(),
Column.mediumText("mediumText").change(),
Column.longText("longText").change(),
Column.date("date").unique().index().default().change(),
Column.datetime("datetime").unique().index().default().change(),
Column.time("time").unique().index().default().change(),
Column.timestamp("timestamp").unique().index().default().change(),
Column.binary("binary").change(),
Column.boolean("boolean").unique().index().default(true).change(),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A").change(),
Column.json("json").change(),
])
)
suite("error in text, blob, json column for default, index and unique"):
suite("create table"):
test("text"):
expect DbError:
rdb.create(
table("TypeIndex", [
Column.text("text").default("A"),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.text("text").index(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.text("text").unique(),
])
)
test("mediumText"):
expect DbError:
rdb.create(
table("TypeIndex", [
Column.mediumText("mediumText").default("A"),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.mediumText("mediumText").index(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.mediumText("mediumText").unique(),
])
)
test("longText"):
expect DbError:
rdb.create(
table("TypeIndex", [
Column.longText("longText").default("A"),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.longText("longText").index(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.longText("longText").unique(),
])
)
test("binary"):
expect DbError:
rdb.create(
table("TypeIndex", [
Column.binary("binary").default("A"),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.binary("binary").index(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.binary("binary").unique(),
])
)
test("json"):
expect DbError:
rdb.create(
table("TypeIndex", [
Column.json("json").default(%*{"key": "value"}),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.json("json").index(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.json("json").unique(),
])
)
suite("add column"):
test("text"):
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.text("text").default("A").add(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.text("text").index().add(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.text("text").unique().add(),
])
)
test("mediumText"):
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.mediumText("mediumText").default("A").add(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.mediumText("mediumText").index().add(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.mediumText("mediumText").unique().add(),
])
)
test("longText"):
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.longText("longText").default("A").add(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.longText("longText").index().add(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.longText("longText").unique().add(),
])
)
test("binary"):
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.binary("binary").default("A").add(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.binary("binary").index().add(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.binary("binary").unique().add(),
])
)
test("json"):
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.json("json").default(%*{"key": "value"}).add(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.json("json").index().add(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.json("json").unique().add(),
])
)
suite("change column"):
test("text"):
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.text("text").default("A").change(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.text("text").index().change(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.text("text").unique().change(),
])
)
test("mediumText"):
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.mediumText("mediumText").default("A").change(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.mediumText("mediumText").index().change(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.mediumText("mediumText").unique().change(),
])
)
test("longText"):
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.longText("longText").default("A").change(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.longText("longText").index().change(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.longText("longText").unique().change(),
])
)
test("binary"):
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.binary("binary").default("A").change(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.binary("binary").index().change(),
])
)
expect DbError:
rdb.create(
table("TypeIndex", [
Column.binary("binary").unique().change(),
])
)
test("json"):
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.json("json").default(%*{"key": "value"}).change(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.json("json").index().change(),
])
)
expect DbError:
rdb.alter(
table("TypeIndex", [
Column.json("json").unique().change(),
])
)
suite("MySQL alter table"):
setup:
rdb.create(
table("IntRelation", [
Column.increments("id"),
]),
table("StrRelation", [
Column.uuid("uuid"),
]),
table("TypeUnique", [
Column.integer("num"),
Column.string("str"),
]),
table("TypeIndex", [
Column.integer("num"),
Column.string("str"),
])
)
test("add columns"):
rdb.alter(
table("TypeIndex", [
Column.increments("id").add(),
Column.integer("integer").unsigned().index().default(1).add(),
Column.smallInteger("smallInteger").unsigned().index().default(1).add(),
Column.mediumInteger("mediumInteger").unsigned().index().default(1).add(),
Column.bigInteger("bigInteger").unsigned().index().default(1).add(),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1).add(),
Column.double("double", 10, 3).unsigned().index().default(1.1).add(),
Column.float("float").unsigned().index().default(1.1).add(),
Column.uuid("uuid").index().default("A").add(),
Column.char("char", 255).index().default("A").add(),
Column.string("string").index().default("A").add(),
Column.text("text").add(),
Column.mediumText("mediumText").add(),
Column.longText("longText").add(),
Column.date("date").index().default().add(),
Column.datetime("datetime").index().default(),
Column.time("time").index().default().add(),
Column.timestamp("timestamp").index().default().add(),
Column.timestamps().add(),
Column.softDelete().add(),
Column.binary("binary").add(),
Column.boolean("boolean").index().default(true).add(),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A").add(),
Column.json("json").add(),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL).add(),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL).add()
]),
table("TypeUnique", [
Column.increments("id").add(),
Column.integer("integer").unsigned().unique().index().default(1).add(),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1).add(),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1).add(),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1).add(),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1).add(),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1).add(),
Column.float("float").unsigned().unique().index().default(1.1).add(),
Column.uuid("uuid").unique().index().default("A").add(),
Column.char("char", 255).unique().index().default("A").add(),
Column.string("string").unique().index().default("A").add(),
Column.text("text").add(),
Column.mediumText("mediumText").add(),
Column.longText("longText").add(),
Column.date("date").unique().index().default().add(),
Column.datetime("datetime").unique().index().default().add(),
Column.time("time").unique().index().default().add(),
Column.timestamp("timestamp").unique().index().default().add(),
Column.timestamps().add(),
Column.softDelete().add(),
Column.binary("binary").add(),
Column.boolean("boolean").unique().index().default(true).add(),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A").add(),
Column.json("json").add()
])
)
test("rename column"):
rdb.alter(
table("TypeIndex", [
Column.renameColumn("num", "num2"),
Column.renameColumn("str", "str2"),
])
)
let columns = rdb.table("TypeIndex").columns().waitFor
check not columns.contains("num")
check columns.contains("num2")
check not columns.contains("str")
check columns.contains("str2")
test("drop column"):
rdb.alter(
table("TypeIndex", [
Column.dropColumn("str")
])
)
let columns = rdb.table("TypeIndex").columns().waitFor
check not columns.contains("str")
test("rename table"):
rdb.drop(
table("TypeIndex_renamed")
)
rdb.table("TypeIndex").insert(%*{"num":1, "str": "a"}).waitFor
rdb.alter(
table("TypeIndex").renameTo("TypeIndex_renamed")
)
var res:Option[JsonNode]
try:
res = rdb.table("TypeIndex").first().waitFor
check not res.isSome
except CatchableError:
check true
res = rdb.table("TypeIndex_renamed").first().waitFor
check res.isSome
test("drop table"):
rdb.create(
table("TypeIndex", [
Column.integer("num")
])
)
rdb.drop(
table("TypeIndex")
)
var res:Option[JsonNode]
try:
res = rdb.table("TypeIndex").first().waitFor
check not res.isSome
except CatchableError:
check true
suite($rdb & " primary"):
test("primary key"):
rdb.create(
table("relation", [
Column.increments("id"),
]),
table("TypeIndex", @[
Column.integer("index1"),
Column.integer("index2"),
Column.string("string"),
Column.foreign("relation_id").reference("id").onTable("relation").onDelete(SET_NULL)
],
primary = @["index1", "index2"]
)
)
suite("Comment"):
test("column comment"):
rdb.create(
table("Comment", [
Column.integer("index1").comment("index1 comment"),
Column.string("string").comment("string comment")
])
)
clearTables(rdb).waitFor()
@@ -0,0 +1,128 @@
discard """
cmd: "nim c --mm:orc -d:reset -r $file"
"""
import std/unittest
import std/json
import std/strformat
import std/strutils
import std/options
import std/asyncdispatch
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = mysql
proc setUp(rdb:MysqlConnections) =
rdb.create([
table("auth",[
Column.increments("id"),
Column.string("auth")
]),
table("user",[
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL)
])
])
# seeder
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
])
.waitFor()
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId
}
)
rdb.table("user").insert(users).waitFor()
suite("raw code transaction"):
setup:
setUp(rdb)
test("commit"):
(proc() {.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
block:
rdb.begin().await
try:
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
rdb.commit().await
except CatchableError:
rdb.rollback().await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "updated"
)().waitFor
test("rollback"):
(proc() {.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
block:
rdb.begin().await
try:
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
raise newException(CatchableError, "")
except:
rdb.rollback().await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
)().waitFor
suite("transaction template"):
setup:
setUp(rdb)
test("commit"):
(proc(){.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
transaction(rdb):
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "updated"
)().waitFor()
test("rollback"):
(proc(){.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
transaction(rdb):
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
raise newException(DbError, "")
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
)().waitFor()
clearTables(rdb).waitFor()
@@ -0,0 +1,10 @@
import std/os
import std/strutils
import ../../src/allographer/connection
let
pgUrl = getEnv("PG_URL")
maxConnections = getEnv("DB_MAX_CONNECTION").parseInt
timeout = getEnv("DB_TIMEOUT").parseInt
let postgres* = dbOpen(PostgreSQL, databaseUrl = asDatabaseUrl(pgUrl), maxConnections, timeout, shouldDisplayLog=true)
@@ -0,0 +1,107 @@
discard """
cmd: "nim c -d:reset $file"
"""
# nim c -d:reset -r tests/postgres/test_create_schema.nim
import std/unittest
import std/asyncdispatch
import std/os
import std/strutils
import std/json
import ../../src/allographer/schema_builder
import ./connections
import ../clear_tables
let rdb = postgres
let schemaFilePath = getCurrentDir() / "schema.nim"
suite "Schema output after migration":
setup:
# schema.nim ファイルの削除
if fileExists(schemaFilePath):
removeFile(schemaFilePath)
rdb.create(
table("int_relation", [
Column.increments("id")
]),
table("str_relation", [
Column.uuid("uuid")
]),
table("test_schema_output", [
Column.increments("id"),
Column.integer("integer"),
Column.smallInteger("smallInteger"),
Column.mediumInteger("mediumInteger"),
Column.bigInteger("bigInteger"),
Column.decimal("decimal", 10, 3),
Column.double("double", 10, 3),
Column.float("float"),
Column.uuid("uuid"),
Column.char("char", 255),
Column.string("string"),
Column.text("text"),
Column.mediumText("mediumText"),
Column.longText("longText"),
Column.date("date"),
Column.datetime("datetime"),
Column.time("time"),
Column.timestamp("timestamp"),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary"),
Column.boolean("boolean"),
Column.enumField("enumField", ["A", "B", "C"]),
Column.json("json"),
Column.foreign("int_relation_id").reference("id").onTable("int_relation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("str_relation").onDelete(SET_NULL)
])
)
test "should generate schema.nim file":
# スキーマ生成
rdb.createSchema(schemaFilePath).waitFor()
# schema.nim ファイルの存在確認
check fileExists(schemaFilePath)
# ファイル内容の検証
let schemaContent = readFile(schemaFilePath)
echo "schemaContent: ", schemaContent
check schemaContent.contains("type TestSchemaOutputTable* = object")
check schemaContent.contains("## test_schema_output")
check schemaContent.contains("integer*: int")
check schemaContent.contains("smallInteger*: int")
check schemaContent.contains("mediumInteger*: int")
check schemaContent.contains("bigInteger*: int")
check schemaContent.contains("decimal*: float")
check schemaContent.contains("double*: float")
check schemaContent.contains("float*: float")
check schemaContent.contains("uuid*: string")
check schemaContent.contains("char*: string")
check schemaContent.contains("string*: string")
check schemaContent.contains("text*: string")
check schemaContent.contains("mediumText*: string")
check schemaContent.contains("longText*: string")
check schemaContent.contains("date*: string")
check schemaContent.contains("datetime*: string")
check schemaContent.contains("time*: string")
check schemaContent.contains("timestamp*: string")
check schemaContent.contains("created_at*: string")
check schemaContent.contains("updated_at*: string")
check schemaContent.contains("deleted_at*: string")
check schemaContent.contains("binary*: string")
check schemaContent.contains("boolean*: bool")
check schemaContent.contains("enumField*: string")
check schemaContent.contains("json*: JsonNode")
check schemaContent.contains("int_relation_id*: int")
check schemaContent.contains("str_relation_id*: string")
clearTables(rdb).waitFor()
# schema.nim ファイルの削除
if fileExists(schemaFilePath):
removeFile(schemaFilePath)
@@ -0,0 +1,45 @@
discard """
cmd: "nim c $file"
"""
# nim c -r tests/v2/postgres/test_open.nim
import std/unittest
import ../../src/allographer/connection
import ../../src/allographer/query_builder
suite("PostgreSQL connection"):
test("connection"):
let database = "database"
let user = "user"
let password = "pass"
let host = "postgres"
let port = 5432
let rdb = dbOpen(PostgreSQL, database, user, password, host, port)
check(rdb.isConnected())
test("connection with URL"):
let url = "postgresql://user:pass@postgres:5432/database"
let rdb = dbOpen(PostgreSQL, url)
check(rdb.isConnected())
test("connection with DatabaseUrl"):
let url = asDatabaseUrl("postgresql://user:pass@postgres:5432/database")
let rdb = dbOpen(PostgreSQL, databaseUrl = url)
check(rdb.isConnected())
test("url is not start with postgresql://"):
let url = "mysql://user:pass@postgres:5432/database"
expect(ValueError):
discard dbOpen(PostgreSQL, url)
test("cannot reach to database"):
let url = "postgresql://john:password@host:0001/database"
expect(DbError):
discard dbOpen(PostgreSQL, url)
@@ -0,0 +1,25 @@
discard """
cmd: "nim c -d:reset -d:ssl -r $file"
"""
import std/[asyncdispatch, os, strutils, unittest]
import ../../src/allographer/connection
import ../../src/allographer/query_builder
suite "PostgreSQL pool waiter (notify)":
test "pool size 1: two concurrent raw gets both complete":
let pgUrl = getEnv("PG_URL")
let timeout = getEnv("DB_TIMEOUT").parseInt
let rdb = dbOpen(PostgreSQL, pgUrl, maxConnections = 1, timeout)
proc sel(): Future[void] {.async.} =
discard await rdb.raw("SELECT 1").get()
let a = sel()
let b = sel()
waitFor all(a, b)
test "pool size 0: raw get raises DbError":
let pgUrl = getEnv("PG_URL")
let rdb = dbOpen(PostgreSQL, pgUrl, maxConnections = 0, timeout = 0)
expect(DbError):
discard waitFor rdb.raw("SELECT 1").get()
@@ -0,0 +1,149 @@
discard """
cmd: "nim c -d:reset -d:ssl -r $file"
"""
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import std/strformat
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
let rdb = postgres
proc setup(rdb: PostgresConnections) =
rdb.create([
table("auth", [
Column.increments("id"),
Column.string("auth")
]),
table("user", [
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.date("submit_on").nullable(),
Column.datetime("submit_at").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL).nullable()
])
])
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
]).waitFor
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
let month = if i > 9: $i else: &"0{i}"
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId,
"submit_on": &"2020-{month}-01",
"submit_at": &"2020-{month}-01 00:00:00",
}
)
rdb.table("user").insert(users).waitFor
setup(rdb)
suite($rdb & " prepared statement"):
test("aged connection refresh"):
rdb.pools.maxConnectionLifetime = 1
rdb.pools.maxConnectionIdleTime = 1
for conn in rdb.pools.conns.mitems:
conn.createdAt = 0
conn.lastUsedAt = 0
let sql = """SELECT "id", "name", "email", "address" FROM "user" WHERE "id" = ?"""
let stmt = rdb.prepare(sql)
defer:
waitFor stmt.close()
let rowOpt = stmt.first(@["1"]).waitFor
check rowOpt.isSome
var refreshed = false
for conn in rdb.pools.conns:
if conn.createdAt > 0:
refreshed = true
break
check refreshed
test("select"):
let stmt = rdb.prepare("""SELECT "id", "name", "email", "address" FROM "user" WHERE "id" = ?""")
defer:
waitFor stmt.close()
let args = newJArray()
args.add(newJInt(1))
let rows = stmt.get(args).waitFor
check rows.len == 1
check rows[0] == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address": newJNull()}
let rowOpt = stmt.first(args).waitFor
let row = options.get(rowOpt)
check row["name"].getStr == "user1"
check stmt.getPlain(args).waitFor[0][1] == "user1"
check stmt.firstPlain(args).waitFor[1] == "user1"
test("logical close and clear cache"):
let sql = """SELECT "id", "name", "email", "address" FROM "user" WHERE "id" = ?"""
let stmt = rdb.prepare(sql)
discard waitFor stmt.first(@["1"])
waitFor stmt.close()
let stmt2 = rdb.prepare(sql)
defer:
waitFor stmt2.close()
let rowOpt = stmt2.first(@["1"]).waitFor
check rowOpt.isSome
waitFor rdb.clearStmtCache()
let stmt3 = rdb.prepare(sql)
defer:
waitFor stmt3.close()
check stmt3.first(@["1"]).waitFor.isSome
test("with prepared connection context"):
let selectStmt = rdb.prepare("""SELECT "id", "name" FROM "user" WHERE "id" = ?""")
let updateStmt = rdb.prepare("""UPDATE "user" SET "address" = ? WHERE "id" = ?""")
defer:
waitFor selectStmt.close()
waitFor updateStmt.close()
waitFor rdb.withConn(
proc(ctx: PostgresPreparedContext): Future[void] {.async.} =
discard await selectStmt.first(ctx, @["1"])
await updateStmt.exec(ctx, @["ctx-address", "1"])
)
let rowOpt = rdb.table("user").find(1).waitFor
let row = options.get(rowOpt)
check row["address"].getStr == "ctx-address"
test("update null"):
let stmt = rdb.prepare("""UPDATE "user" SET "address" = ? WHERE "id" = ?""")
defer:
waitFor stmt.close()
waitFor stmt.exec(@["NULL", "1"])
let rowOpt = rdb.table("user").find(1).waitFor
let row = options.get(rowOpt)
check row["address"].kind == JNull
@@ -0,0 +1,561 @@
discard """
cmd: "nim c -d:reset -d:ssl -r $file"
"""
# nim c -d:reset -d:ssl -r tests/postgres/test_query.nim
import std/unittest
import std/asyncdispatch
import std/httpclient
import std/json
import std/options
import std/streams
import std/strformat
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = postgres
# =============================================================================
# test
# =============================================================================
proc setup(rdb:PostgresConnections) =
rdb.create([
table("auth",[
Column.increments("id"),
Column.string("auth")
]),
table("user",[
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.date("submit_on").nullable(),
Column.datetime("submit_at").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL).nullable()
])
])
# seeder
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
])
.waitFor
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
let month = if i > 9: $i else: &"0{i}"
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId,
"submit_on": &"2020-{month}-01",
"submit_at": &"2020-{month}-01 00:00:00",
}
)
rdb.table("user").insert(users).waitFor
setup(rdb)
suite($rdb & " get"):
test("get"):
let t = rdb.table("user").get().waitFor
let expect = %*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1}
echo t[0]
echo expect
check t[0] == expect
test("getPlain"):
let t = rdb.table("user").getPlain().waitFor
check t[0] == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("first"):
var t = rdb.table("user").where("name", "=", "user1").first().waitFor().get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("firstPlain"):
var t = rdb.table("user").firstPlain().waitFor
check t == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("find"):
var t = rdb.table("user").find(1).waitFor.get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("findString"):
var t = rdb.table("user").find("1").waitFor.get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("findPlain"):
var t = rdb.table("user").findPlain(1).waitFor
check t == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("columns"):
let columns = rdb.table("user").columns().waitFor
check columns == @["id", "name", "email", "address", "submit_on", "submit_at", "auth_id"]
test("select"):
var t = rdb.select("name", "email").table("user").get().waitFor
check t[0] == %*{"name": "user1", "email": "user1@example.com"}
test("selectAs"):
var t = rdb.select("name as user_name", "email").table("user").get().waitFor
check t[0] == %*{"user_name": "user1", "email": "user1@example.com"}
test("selectLike"):
var t = rdb.select("email").table("user").where("email", "LIKE", "%10%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
t = rdb.select("email").table("user").where("email", "LIKE", "user10%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
t = rdb.select("email").table("user").where("email", "LIKE", "%10@example.com%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
test("select count"):
var t = rdb.select("COUNT(id) as count").table("user").get().waitFor
check t[0]["count"].getInt() == 10
test("where"):
var t = rdb.table("user").where("auth_id", "=", "1").get().waitFor
check t == @[
%*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1},
%*{"id":3,"name":"user3","email":"user3@example.com","address":newJNull(),"submit_on":"2020-03-01","submit_at":"2020-03-01 00:00:00","auth_id":1},
%*{"id":5,"name":"user5","email":"user5@example.com","address":newJNull(),"submit_on":"2020-05-01","submit_at":"2020-05-01 00:00:00","auth_id":1},
%*{"id":7,"name":"user7","email":"user7@example.com","address":newJNull(),"submit_on":"2020-07-01","submit_at":"2020-07-01 00:00:00","auth_id":1},
%*{"id":9,"name":"user9","email":"user9@example.com","address":newJNull(),"submit_on":"2020-09-01","submit_at":"2020-09-01 00:00:00","auth_id":1}
]
test("orWhere"):
var t = rdb.table("user").where("auth_id", "=", "1").orWhere("name", "=", "user2").get().waitFor
check t == @[
%*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1},
%*{"id":2,"name":"user2","email":"user2@example.com","address":newJNull(),"submit_on":"2020-02-01","submit_at":"2020-02-01 00:00:00","auth_id":2},
%*{"id":3,"name":"user3","email":"user3@example.com","address":newJNull(),"submit_on":"2020-03-01","submit_at":"2020-03-01 00:00:00","auth_id":1},
%*{"id":5,"name":"user5","email":"user5@example.com","address":newJNull(),"submit_on":"2020-05-01","submit_at":"2020-05-01 00:00:00","auth_id":1},
%*{"id":7,"name":"user7","email":"user7@example.com","address":newJNull(),"submit_on":"2020-07-01","submit_at":"2020-07-01 00:00:00","auth_id":1},
%*{"id":9,"name":"user9","email":"user9@example.com","address":newJNull(),"submit_on":"2020-09-01","submit_at":"2020-09-01 00:00:00","auth_id":1}
]
test("whereBetween"):
var t = rdb
.select("id", "name")
.table("user")
.where("auth_id", "=", 1)
.whereBetween("id", [6, 9])
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user7"
test("whereBetweenString"):
var t = rdb
.table("user")
.whereBetween("submit_at", ["2020-01-01 00:00:00", "2020-01-31 00:00:00"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
t = rdb
.table("user")
.whereBetween("submit_on", ["2020-01-01", "2020-01-31"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
test("whereNotBetween"):
var t = rdb
.select("id", "name")
.table("user")
.where("auth_id", "=", 1)
.whereNotBetween("id", [1, 4])
.get()
.waitFor
check t[0]["name"].getStr() == "user5"
test("whereNotBetweenString"):
var t = rdb
.table("user")
.whereNotBetween("submit_at", ["2020-2-1 00:00:00", "2020-12-31 00:00:00"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
t = rdb
.table("user")
.whereNotBetween("submit_on", ["2020-2-1", "2020-12-31"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
test("whereIn"):
var t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereIn("id", @[5, 6, 7])
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user5"
t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereIn("name", @["user5", "user6", "user7"])
.get()
.waitFor
check t[0]["name"].getStr() == "user5"
test("whereNotIn"):
var t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereNotIn("id", @[5, 6, 7])
.get()
.waitFor
echo t
check t == @[
%*{"id":4, "name": "user4"},
%*{"id":8, "name": "user8"},
%*{"id":9, "name": "user9"},
%*{"id":10, "name": "user10"},
]
t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereNotIn("name", @["user5", "user6", "user7"])
.get()
.waitFor
echo t
check t == @[
%*{"id":4, "name": "user4"},
%*{"id":8, "name": "user8"},
%*{"id":9, "name": "user9"},
%*{"id":10, "name": "user10"},
]
test("whereNull"):
setUp(rdb)
rdb.table("user").insert(%*{"email": "user11@example.com"}).waitFor
var t = rdb
.select("id", "name", "email")
.table("user")
.whereNull("name")
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 11
test("groupBy"):
var t = rdb
.table("user")
.groupBy("auth_id")
.max("id")
.waitFor()
.get()
echo t
check t == "11"
test("having"):
var t = rdb
.select("id", "name")
.table("user")
.groupBy("auth_id")
.groupBy("id")
.having("auth_id", "=", 1)
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 1
test("orderBy"):
var t = rdb.table("user")
.where("auth_id", "is not", nil)
.orderBy("auth_id", Asc)
.orderBy("id", Desc)
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 9
test("join"):
var t = rdb
.select("user.id", "user.name")
.table("user")
.join("auth", "auth.id", "=", "user.auth_id")
.where("auth.id", "=", "2")
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user2"
test("leftJoin"):
rdb.table("user").insert(%*{
"name": "user11"
})
.waitFor
var t = rdb
.select("user.id", "user.name", "user.auth_id")
.table("user")
.leftJoin("auth", "auth.id", "=", "user.auth_id")
.orderBy("user.id", Desc)
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user11"
check t[0]["auth_id"] == newJNull()
test("resultIsNull"):
check rdb.table("user").find(50).waitFor().isSome == false
check newSeq[JsonNode](0) == rdb.table("user").where("id", "=", 50).get().waitFor
suite($rdb & " insert"):
setup:
setup(rdb)
test("insert row"):
rdb.table("user").insert(%*{"name": "Alice"}).waitFor
let t = rdb.table("user").orderBy("id", Desc).first().waitFor().get()
check t["name"].getStr() == "Alice"
test("insert rows"):
rdb.table("user").insert(@[
%*{"name": "Alice"},
%*{"name": "Bob"},
]).waitFor
let t = rdb.table("user").orderBy("id", Desc).limit(2).get().waitFor()
check t[0]["name"].getStr() == "Bob"
check t[1]["name"].getStr() == "Alice"
test("insertId row"):
var id = rdb.table("user").insertId(%*{"name": "Alice"}).waitFor
echo id
let t = rdb.table("user").find(id).waitFor().get()
check t["name"].getStr() == "Alice"
test("insertId rows"):
let ids = rdb.table("user").insertId(@[
%*{"name": "Alice"},
%*{"name": "Bob"},
]).waitFor
var t = rdb.table("user").find(ids[0]).waitFor().get()
check t["name"].getStr == "Alice"
t = rdb.table("user").find(ids[1]).waitFor().get()
check t["name"].getStr == "Bob"
test("insertNil"):
var id = rdb.table("user").insertId(%*{
"name": "Alice",
"email": nil,
"address": ""
})
.waitFor
var res = rdb.table("user").find(id).waitFor
echo res.get
check res.get["email"] == newJNull()
res = rdb.table("user").where("email", "is", nil).first().waitFor
echo res.get
check res.get["email"] == newJNull()
test("insert [T]"):
type User = object
name:string
let user = User(name: "Alice")
rdb.table("user").insert(user).waitFor
let row = rdb.table("user").orderBy("id", Desc).first().waitFor().get()
check row["name"].getStr() == "Alice"
test("insert seq[T]"):
type User = object
name:string
let users = @[
User(name: "Alice"),
User(name: "Bob"),
]
rdb.table("user").insert(users).waitFor
let t = rdb.table("user").orderBy("id", Desc).limit(2).get().waitFor()
check t[0]["name"].getStr() == "Bob"
check t[1]["name"].getStr() == "Alice"
suite($rdb & " update"):
setup:
setup(rdb)
test("update"):
rdb.table("user").where("id", "=", 1).update(%*{"name": "Alice"}).waitFor
var t = rdb.table("user").find(1).waitFor().get()
check t["name"].getStr() == "Alice"
test("update [T]"):
type User = object
name:string
let user = User(name: "Alice")
rdb.table("user").where("id", "=", 1).update(user).waitFor
var t = rdb.table("user").find(1).waitFor().get()
check t["name"].getStr() == "Alice"
suite($rdb & " delete"):
setup(rdb)
test("delete"):
rdb.table("user").delete(1).waitFor
check rdb.table("user").find(1).waitFor.isSome == false
test("delete where"):
rdb.table("user").where("name", "=", "user1").delete().waitFor
check rdb.table("user").find(1).waitFor.isSome == false
suite($rdb & " rawQuery"):
setup(rdb)
test("get"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT * FROM \"user\" WHERE \"id\" = ?"
let res = rdb.raw(sql, %*[1]).get().waitFor
echo res
check res[0]["name"].getStr == "user1"
test("getPlain"):
let sql = &"SELECT * FROM \"user\" WHERE \"id\" = ?"
let res = rdb.raw(sql, %*[1]).getPlain().waitFor
echo res
check res[0][1] == "user1"
test("first"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT * FROM \"user\" WHERE \"id\" = ?"
let res = rdb.raw(sql, %*[1]).first().waitFor().get()
echo res
check res["name"].getStr == "user1"
test("firstPlain"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT name FROM \"user\" WHERE \"id\" = ?"
let res = rdb.raw(sql, %*[1]).firstPlain().waitFor()
echo res
check res[0] == "user1"
test("exec"):
var sql = "UPDATE \"user\" SET \"name\" = ? WHERE \"id\" = ?"
rdb.raw(sql, %*["updated user1", 1]).exec().waitFor
sql = &"SELECT * FROM \"user\" WHERE \"id\" = ?"
let res = rdb.raw(sql, %[1]).get().waitFor
echo res
check res[0]["name"].getStr == "updated user1"
setup(rdb)
suite($rdb & " aggregates"):
test("count"):
var t = rdb.table("user").count().waitFor
check t == 10
test("max"):
var t = rdb.table("user").max("name").waitFor.get
check t == "user9"
var t2 = rdb.table("user").max("id").waitFor.get
check t2 == "10"
test("min"):
var t = rdb.table("user").min("name").waitFor.get
check t == "user1"
var t2 = rdb.table("user").min("id").waitFor.get
check t2 == "1"
test("avg"):
var t = rdb.table("user").avg("id").waitFor.get
check t == 5.5
test("sum"):
var t = rdb.table("user").sum("id").waitFor.get
check t == 55.0
suite($rdb & " binary data"):
test("insert binary"):
rdb.create(
table("test", [
Column.increments("id"),
Column.binary("pic"),
])
)
let client = newAsyncHttpClient()
let response = client.getContent("https://nim-lang.org/assets/img/twitter_banner.png").waitFor()
let imageStream = newStringStream(response)
let binaryImage = imageStream.readAll()
let id = rdb.table("test").insertId(%*{"pic": binaryImage}).waitFor()
var res = rdb.table("test").find(id).waitFor().get()
check res["pic"].getStr().len > 0
rdb.table("test").where("id","=", id).update(%*{"pic": binaryImage}).waitFor()
res = rdb.table("test").find(id).waitFor().get()
check res["pic"].getStr().len > 0
clearTables(rdb).waitFor()
@@ -0,0 +1,332 @@
discard """
cmd: "nim c -d:reset $file"
"""
# nim c -d:reset -r tests/postgres/test_schema.nim
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = postgres
clearTables(rdb).waitFor()
suite("PostgreSQL create table"):
test("create table"):
rdb.create(
table("IntRelation", [
Column.increments("id")
]),
table("StrRelation", [
Column.uuid("uuid")
]),
table("TypeIndex", [
Column.increments("id"),
Column.integer("integer").unsigned().index().default(1),
Column.smallInteger("smallInteger").unsigned().index().default(1),
Column.mediumInteger("mediumInteger").unsigned().index().default(1),
Column.bigInteger("bigInteger").unsigned().index().default(1),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1),
Column.double("double", 10, 3).unsigned().index().default(1.1),
Column.float("float").unsigned().index().default(1.1),
Column.uuid("uuid").index().default("A"),
Column.char("char", 255).index().default("A"),
Column.string("string").index().default("A"),
Column.text("text").index().default("A"),
Column.mediumText("mediumText").index().default("A"),
Column.longText("longText").index().default("A"),
Column.date("date").index().default(),
Column.datetime("datetime").index().default(),
Column.time("time").index().default(),
Column.timestamp("timestamp").index().default(),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary").index().default("A"),
Column.boolean("boolean").index().default(true),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A"),
Column.json("json").index().default(%*{"key":"value"}),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL)
]),
table("TypeUnique", [
Column.increments("id"),
Column.integer("integer").unsigned().unique().index().default(1),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1),
Column.float("float").unsigned().unique().index().default(1.1),
Column.uuid("uuid").unique().index().default("A"),
Column.char("char", 255).unique().index().default("A"),
Column.string("string").unique().index().default("A"),
Column.text("text").unique().index().default("A"),
Column.mediumText("mediumText").unique().index().default("A"),
Column.longText("longText").unique().index().default("A"),
Column.date("date").unique().index().default(),
Column.datetime("datetime").unique().index().default(),
Column.time("time").unique().index().default(),
Column.timestamp("timestamp").unique().index().default(),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary").unique().index().default("A"),
Column.boolean("boolean").unique().index().default(true),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A"),
Column.json("json").unique().index().default(%*{"key":"value"}),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL)
])
)
test("change columns"):
rdb.alter(
table("TypeIndex", [
Column.integer("integer").unsigned().index().default(1).change(),
Column.smallInteger("smallInteger").unsigned().index().default(1).change(),
Column.mediumInteger("mediumInteger").unsigned().index().default(1).change(),
Column.bigInteger("bigInteger").unsigned().index().default(1).change(),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1).change(),
Column.double("double", 10, 3).unsigned().index().default(1.1).change(),
Column.float("float").unsigned().index().default(1.1).change(),
Column.uuid("uuid").index().default("A").change(),
Column.char("char", 255).index().default("A").change(),
Column.string("string").index().default("A").change(),
Column.text("text").index().default("A").change(),
Column.mediumText("mediumText").index().default("A").change(),
Column.longText("longText").index().default("A").change(),
Column.date("date").index().default().change(),
Column.datetime("datetime").index().default().change(),
Column.time("time").index().default().change(),
Column.timestamp("timestamp").index().default().change(),
Column.binary("binary").index().default("A").change(),
Column.boolean("boolean").index().default(true).change(),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A").change(),
Column.json("json").index().default(%*{"key":"value"}).change(),
]),
table("TypeUnique", [
Column.integer("integer").unsigned().unique().index().default(1).change(),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1).change(),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1).change(),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1).change(),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1).change(),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1).change(),
Column.float("float").unsigned().unique().index().default(1.1).change(),
Column.uuid("uuid").unique().index().default("A").change(),
Column.char("char", 255).unique().index().default("A").change(),
Column.string("string").unique().index().default("A").change(),
Column.text("text").unique().index().default("A").change(),
Column.mediumText("mediumText").unique().index().default("A").change(),
Column.longText("longText").unique().index().default("A").change(),
Column.date("date").unique().index().default().change(),
Column.datetime("datetime").unique().index().default().change(),
Column.time("time").unique().index().default().change(),
Column.timestamp("timestamp").unique().index().default().change(),
Column.binary("binary").unique().index().default("A").change(),
Column.boolean("boolean").unique().index().default(true).change(),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A").change(),
Column.json("json").unique().index().default(%*{"key":"value"}).change(),
])
)
suite($rdb & " alter table"):
setup:
rdb.create(
table("IntRelation", [
Column.increments("id"),
]),
table("StrRelation", [
Column.uuid("uuid"),
]),
table("TypeUnique", [
Column.integer("num"),
Column.string("str"),
]),
table("TypeIndex", [
Column.integer("num"),
Column.string("str"),
])
)
test("add columns"):
rdb.alter(
table("TypeIndex", [
Column.increments("id").add(),
Column.integer("integer").unsigned().index().default(1).add(),
Column.smallInteger("smallInteger").unsigned().index().default(1).add(),
Column.mediumInteger("mediumInteger").unsigned().index().default(1).add(),
Column.bigInteger("bigInteger").unsigned().index().default(1).add(),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1).add(),
Column.double("double", 10, 3).unsigned().index().default(1.1).add(),
Column.float("float").unsigned().index().default(1.1).add(),
Column.uuid("uuid").index().default("A").add(),
Column.char("char", 255).index().default("A").add(),
Column.string("string").index().default("A").add(),
Column.text("text").index().default("A").add(),
Column.mediumText("mediumText").index().default("A").add(),
Column.longText("longText").index().default("A").add(),
Column.date("date").index().default().add(),
Column.datetime("datetime").index().default(),
Column.time("time").index().default().add(),
Column.timestamp("timestamp").index().default().add(),
Column.timestamps().add(),
Column.softDelete().add(),
Column.binary("binary").index().default("A").add(),
Column.boolean("boolean").index().default(true).add(),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A").add(),
Column.json("json").index().default(%*{"key":"value"}).add(),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL).add(),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL).add()
]),
table("TypeUnique", [
Column.increments("id").add(),
Column.integer("integer").unsigned().unique().index().default(1).add(),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1).add(),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1).add(),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1).add(),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1).add(),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1).add(),
Column.float("float").unsigned().unique().index().default(1.1).add(),
Column.uuid("uuid").unique().index().default("A").add(),
Column.char("char", 255).unique().index().default("A").add(),
Column.string("string").unique().index().default("A").add(),
Column.text("text").unique().index().default("A").add(),
Column.mediumText("mediumText").unique().index().default("A").add(),
Column.longText("longText").unique().index().default("A").add(),
Column.date("date").unique().index().default().add(),
Column.datetime("datetime").unique().index().default().add(),
Column.time("time").unique().index().default().add(),
Column.timestamp("timestamp").unique().index().default().add(),
Column.timestamps().add(),
Column.softDelete().add(),
Column.binary("binary").unique().index().default("A").add(),
Column.boolean("boolean").unique().index().default(true).add(),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A").add(),
Column.json("json").unique().index().default(%*{"key":"value"}).add()
])
)
test("rename column"):
rdb.alter(
table("TypeIndex", [
Column.renameColumn("num", "num2"),
Column.renameColumn("str", "str2"),
])
)
let columns = rdb.table("TypeIndex").columns().waitFor
check not columns.contains("num")
check columns.contains("num2")
check not columns.contains("str")
check columns.contains("str2")
test("drop column"):
rdb.alter(
table("TypeIndex", [
Column.dropColumn("str")
])
)
let columns = rdb.table("TypeIndex").columns().waitFor
check not columns.contains("str")
test("rename table"):
rdb.drop(
table("TypeIndex_renamed")
)
rdb.table("TypeIndex").insert(%*{"num":1, "str": "a"}).waitFor
rdb.alter(
table("TypeIndex").renameTo("TypeIndex_renamed")
)
var res:Option[JsonNode]
try:
res = rdb.table("TypeIndex").first().waitFor
check not res.isSome
except CatchableError:
check true
res = rdb.table("TypeIndex_renamed").first().waitFor
check res.isSome
test("drop table"):
rdb.create(
table("TypeIndex", [
Column.integer("num")
])
)
rdb.drop(
table("TypeIndex")
)
var res:Option[JsonNode]
try:
res = rdb.table("TypeIndex").first().waitFor
check not res.isSome
except CatchableError:
check true
suite($rdb & " primary"):
test("primary key"):
rdb.create([
table("relation", [
Column.increments("id"),
]),
table("TypeIndex", @[
Column.integer("index1"),
Column.integer("index2"),
Column.string("string"),
Column.foreign("relation_id").reference("id").onTable("relation").onDelete(SET_NULL)
],
primary = @["index1", "index2"]
)
])
suite("Comment"):
test("create table with column comment"):
rdb.create(
table("Comment", [
Column.integer("index1").comment("index1 comment"),
Column.string("string").comment("string comment")
])
)
test("alter table change column comment"):
rdb.alter(
table("Comment", [
Column.integer("index1").comment("changed index1 comment").change()
])
)
test("alter table add column comment"):
rdb.alter(
table("Comment", [
Column.string("string2").comment("string2 comment").add()
])
)
test("alter table add null comment"):
rdb.alter(
table("Comment", [
Column.string("string").comment(nil).change()
])
)
clearTables(rdb).waitFor()
@@ -0,0 +1,131 @@
discard """
cmd: "nim c --mm:orc -d:reset -r $file"
"""
# nim c -r -d:reset tests/v2/postgres/test_transaction.nim
import std/unittest
import std/json
import std/strformat
import std/strutils
import std/options
import std/asyncdispatch
import std/macros
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = postgres
proc setUp(rdb:PostgresConnections) =
rdb.create([
table("auth",[
Column.increments("id"),
Column.string("auth")
]),
table("user",[
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL)
])
])
# seeder
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
])
.waitFor()
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId
}
)
rdb.table("user").insert(users).waitFor()
suite("raw code transaction"):
setup:
setUp(rdb)
test("commit"):
(proc() {.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
block:
rdb.begin().await
try:
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
rdb.commit().await
except CatchableError:
rdb.rollback().await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "updated"
)().waitFor
test("rollback"):
(proc() {.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
block:
rdb.begin().await
try:
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
raise newException(CatchableError, "")
except:
rdb.rollback().await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
)().waitFor
suite("transaction template"):
setup:
setUp(rdb)
test("commit"):
(proc(){.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
transaction(rdb):
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "updated"
)().waitFor()
test("rollback"):
(proc(){.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
transaction(rdb):
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
raise newException(DbError, "")
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
)().waitFor()
clearTables(rdb).waitFor()
@@ -0,0 +1,11 @@
import std/os
import std/strutils
import ../../src/allographer/connection
let
sqliteHost = getEnv("SQLITE_HOST")
maxConnections = getEnv("DB_MAX_CONNECTION").parseInt
timeout = getEnv("DB_TIMEOUT").parseInt
echo "sqliteHost: ", sqliteHost
let sqlite* = dbOpen(SQLite3, sqliteHost, maxConnections, timeout, shouldDisplayLog=true)
@@ -0,0 +1,107 @@
discard """
cmd: "nim c -d:reset $file"
"""
# nim c -d:reset -r tests/sqlite/test_create_schema.nim
import std/unittest
import std/asyncdispatch
import std/os
import std/strutils
import std/json
import ../../src/allographer/schema_builder
import ./connections
import ../clear_tables
let rdb = sqlite
let schemaFilePath = getCurrentDir() / "schema.nim"
suite "Schema output after migration":
setup:
# schema.nim ファイルの削除
if fileExists(schemaFilePath):
removeFile(schemaFilePath)
rdb.create(
table("int_relation", [
Column.increments("id")
]),
table("str_relation", [
Column.uuid("uuid")
]),
table("test_schema_output", [
Column.increments("id"),
Column.integer("integer"),
Column.smallInteger("smallInteger"),
Column.mediumInteger("mediumInteger"),
Column.bigInteger("bigInteger"),
Column.decimal("decimal", 10, 3),
Column.double("double", 10, 3),
Column.float("float"),
Column.uuid("uuid"),
Column.char("char", 255),
Column.string("string"),
Column.text("text"),
Column.mediumText("mediumText"),
Column.longText("longText"),
Column.date("date"),
Column.datetime("datetime"),
Column.time("time"),
Column.timestamp("timestamp"),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary"),
Column.boolean("boolean"),
Column.enumField("enumField", ["A", "B", "C"]),
Column.json("json"),
Column.foreign("int_relation_id").reference("id").onTable("int_relation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("str_relation").onDelete(SET_NULL)
])
)
test "should generate schema.nim file":
# スキーマ生成
rdb.createSchema(schemaFilePath).waitFor()
# schema.nim ファイルの存在確認
check fileExists(schemaFilePath)
# ファイル内容の検証
let schemaContent = readFile(schemaFilePath)
echo "schemaContent: ", schemaContent
check schemaContent.contains("type TestSchemaOutputTable* = object")
check schemaContent.contains("## test_schema_output")
check schemaContent.contains("integer*: int")
check schemaContent.contains("smallInteger*: int")
check schemaContent.contains("mediumInteger*: int")
check schemaContent.contains("bigInteger*: int")
check schemaContent.contains("decimal*: float")
check schemaContent.contains("double*: float")
check schemaContent.contains("float*: float")
check schemaContent.contains("uuid*: string")
check schemaContent.contains("char*: string")
check schemaContent.contains("string*: string")
check schemaContent.contains("text*: string")
check schemaContent.contains("mediumText*: string")
check schemaContent.contains("longText*: string")
check schemaContent.contains("date*: string")
check schemaContent.contains("datetime*: string")
check schemaContent.contains("time*: string")
check schemaContent.contains("timestamp*: string")
check schemaContent.contains("created_at*: string")
check schemaContent.contains("updated_at*: string")
check schemaContent.contains("deleted_at*: string")
check schemaContent.contains("binary*: string")
check schemaContent.contains("boolean*: bool")
check schemaContent.contains("enumField*: string")
check schemaContent.contains("json*: JsonNode")
check schemaContent.contains("int_relation_id*: int")
check schemaContent.contains("str_relation_id*: string")
clearTables(rdb).waitFor()
# schema.nim ファイルの削除
if fileExists(schemaFilePath):
removeFile(schemaFilePath)
@@ -0,0 +1,21 @@
discard """
cmd: "nim c $file"
"""
import std/os
import std/unittest
import ../../src/allographer/connection
suite("SQLite connection"):
test("connection"):
let database = getTempDir() / "allographer-open-test.sqlite3"
let rdb = dbOpen(SQLite3, database, shouldDisplayLog=false)
check(rdb.pools.conns.len > 0)
test("connection with DatabaseUrl"):
let database = getTempDir() / "allographer-open-test-url.sqlite3"
let url = asDatabaseUrl("sqlite:///" & database)
let rdb = dbOpen(SQLite3, databaseUrl = url)
check(rdb.pools.conns.len > 0)
@@ -0,0 +1,25 @@
discard """
cmd: "nim c -d:reset -d:ssl -r $file"
"""
import std/[asyncdispatch, os, strutils, unittest]
import ../../src/allographer/connection
import ../../src/allographer/query_builder
suite "SQLite pool waiter (notify)":
test "pool size 1: two concurrent raw gets both complete":
let sqliteHost = getEnv("SQLITE_HOST")
let timeout = getEnv("DB_TIMEOUT").parseInt
let rdb = dbOpen(SQLite3, sqliteHost, maxConnections = 1, timeout)
proc sel(): Future[void] {.async.} =
discard await rdb.raw("SELECT 1").get()
let a = sel()
let b = sel()
waitFor all(a, b)
test "pool size 0: raw get raises DbError":
let sqliteHost = getEnv("SQLITE_HOST")
let rdb = dbOpen(SQLite3, sqliteHost, maxConnections = 0, timeout = 0)
expect(DbError):
discard waitFor rdb.raw("SELECT 1").get()
@@ -0,0 +1,149 @@
discard """
cmd: "nim c -d:reset -d:ssl -r $file"
"""
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import std/strformat
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
let rdb = sqlite
proc setup(rdb: SqliteConnections) =
rdb.create([
table("auth", [
Column.increments("id"),
Column.string("auth")
]),
table("user", [
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.date("submit_on").nullable(),
Column.datetime("submit_at").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL).nullable()
])
])
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
]).waitFor
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
let month = if i > 9: $i else: &"0{i}"
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId,
"submit_on": &"2020-{month}-01",
"submit_at": &"2020-{month}-01 00:00:00",
}
)
rdb.table("user").insert(users).waitFor
setup(rdb)
suite($rdb & " prepared statement"):
test("aged connection refresh"):
rdb.pools.maxConnectionLifetime = 1
rdb.pools.maxConnectionIdleTime = 1
for conn in rdb.pools.conns.mitems:
conn.createdAt = 0
conn.lastUsedAt = 0
let sql = """SELECT "id", "name", "email", "address" FROM "user" WHERE "id" = ?"""
let stmt = rdb.prepare(sql)
defer:
waitFor stmt.close()
let rowOpt = stmt.first(@["1"]).waitFor
check rowOpt.isSome
var refreshed = false
for conn in rdb.pools.conns:
if conn.createdAt > 0:
refreshed = true
break
check refreshed
test("select"):
let stmt = rdb.prepare("""SELECT "id", "name", "email", "address" FROM "user" WHERE "id" = ?""")
defer:
waitFor stmt.close()
let args = newJArray()
args.add(newJInt(1))
let rows = stmt.get(args).waitFor
check rows.len == 1
check rows[0] == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address": newJNull()}
let rowOpt = stmt.first(args).waitFor
let row = options.get(rowOpt)
check row["name"].getStr == "user1"
check stmt.getPlain(args).waitFor[0][1] == "user1"
check stmt.firstPlain(args).waitFor[1] == "user1"
test("logical close and clear cache"):
let sql = """SELECT "id", "name", "email", "address" FROM "user" WHERE "id" = ?"""
let stmt = rdb.prepare(sql)
discard waitFor stmt.first(@["1"])
waitFor stmt.close()
let stmt2 = rdb.prepare(sql)
defer:
waitFor stmt2.close()
let rowOpt = stmt2.first(@["1"]).waitFor
check rowOpt.isSome
waitFor rdb.clearStmtCache()
let stmt3 = rdb.prepare(sql)
defer:
waitFor stmt3.close()
check stmt3.first(@["1"]).waitFor.isSome
test("with prepared connection context"):
let selectStmt = rdb.prepare("""SELECT "id", "name" FROM "user" WHERE "id" = ?""")
let updateStmt = rdb.prepare("""UPDATE "user" SET "address" = ? WHERE "id" = ?""")
defer:
waitFor selectStmt.close()
waitFor updateStmt.close()
waitFor rdb.withConn(
proc(ctx: SqlitePreparedContext): Future[void] {.async.} =
discard await selectStmt.first(ctx, @["1"])
await updateStmt.exec(ctx, @["ctx-address", "1"])
)
let rowOpt = rdb.table("user").find(1).waitFor
let row = options.get(rowOpt)
check row["address"].getStr == "ctx-address"
test("update null"):
let stmt = rdb.prepare("""UPDATE "user" SET "address" = ? WHERE "id" = ?""")
defer:
waitFor stmt.close()
waitFor stmt.exec(@["NULL", "1"])
let rowOpt = rdb.table("user").find(1).waitFor
let row = options.get(rowOpt)
check row["address"].kind == JNull
@@ -0,0 +1,568 @@
discard """
cmd: "nim c -d:reset -d:ssl -r $file"
"""
# nim c -d:reset -d:ssl -r tests/sqlite/test_query.nim
import std/unittest
import std/asyncdispatch
import std/httpclient
import std/json
import std/options
import std/streams
import std/strformat
import std/strutils
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = sqlite
# =============================================================================
# test
# =============================================================================
proc setup(rdb:SqliteConnections) =
rdb.create([
table("auth",[
Column.increments("id"),
Column.string("auth")
]),
table("user",[
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.date("submit_on").nullable(),
Column.datetime("submit_at").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL).nullable()
])
])
# seeder
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
])
.waitFor
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
let month = if i > 9: $i else: &"0{i}"
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId,
"submit_on": &"2020-{month}-01",
"submit_at": &"2020-{month}-01 00:00:00",
}
)
rdb.table("user").insert(users).waitFor
setup(rdb)
suite($rdb & " get"):
test("get"):
let t = rdb.table("user").get().waitFor
let expect = %*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1}
echo t[0]
echo expect
check t[0] == expect
test("getPlain"):
let t = rdb.table("user").getPlain().waitFor
check t[0] == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("first"):
var t = rdb.table("user").where("name", "=", "user1").first().waitFor().get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("firstPlain"):
var t = rdb.table("user").firstPlain().waitFor
check t == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("find"):
var t = rdb.table("user").find(1).waitFor.get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("findString"):
var t = rdb.table("user").find("1").waitFor.get
check t == %*{"id": 1, "name": "user1", "email": "user1@example.com", "address":newJNull(), "submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00", "auth_id": 1}
test("findPlain"):
var t = rdb.table("user").findPlain(1).waitFor
check t == @["1", "user1", "user1@example.com", "", "2020-01-01", "2020-01-01 00:00:00", "1"]
test("columns"):
let columns = rdb.table("user").columns().waitFor
check columns == @["id", "name", "email", "address", "submit_on", "submit_at", "auth_id"]
test("select"):
var t = rdb.select("name", "email").table("user").get().waitFor
check t[0] == %*{"name": "user1", "email": "user1@example.com"}
test("selectAs"):
var t = rdb.select("name as user_name", "email").table("user").get().waitFor
check t[0] == %*{"user_name": "user1", "email": "user1@example.com"}
test("selectLike"):
var t = rdb.select("email").table("user").where("email", "LIKE", "%10%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
t = rdb.select("email").table("user").where("email", "LIKE", "user10%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
t = rdb.select("email").table("user").where("email", "LIKE", "%10@example.com%").get().waitFor
check t == @[%*{"email":"user10@example.com"}]
test("select count"):
var t = rdb.select("COUNT(id) as count").table("user").get().waitFor
check t[0]["count"].getStr() == "10"
test("select max"):
var t = rdb.select("MAX(id) as max").table("user").get().waitFor
check t[0]["max"].getStr() == "10"
test("where"):
var t = rdb.table("user").where("auth_id", "=", "1").get().waitFor
check t == @[
%*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1},
%*{"id":3,"name":"user3","email":"user3@example.com","address":newJNull(),"submit_on":"2020-03-01","submit_at":"2020-03-01 00:00:00","auth_id":1},
%*{"id":5,"name":"user5","email":"user5@example.com","address":newJNull(),"submit_on":"2020-05-01","submit_at":"2020-05-01 00:00:00","auth_id":1},
%*{"id":7,"name":"user7","email":"user7@example.com","address":newJNull(),"submit_on":"2020-07-01","submit_at":"2020-07-01 00:00:00","auth_id":1},
%*{"id":9,"name":"user9","email":"user9@example.com","address":newJNull(),"submit_on":"2020-09-01","submit_at":"2020-09-01 00:00:00","auth_id":1}
]
test("orWhere"):
var t = rdb.table("user").where("auth_id", "=", "1").orWhere("name", "=", "user2").get().waitFor
check t == @[
%*{"id":1,"name":"user1","email":"user1@example.com","address":newJNull(),"submit_on":"2020-01-01","submit_at":"2020-01-01 00:00:00","auth_id":1},
%*{"id":2,"name":"user2","email":"user2@example.com","address":newJNull(),"submit_on":"2020-02-01","submit_at":"2020-02-01 00:00:00","auth_id":2},
%*{"id":3,"name":"user3","email":"user3@example.com","address":newJNull(),"submit_on":"2020-03-01","submit_at":"2020-03-01 00:00:00","auth_id":1},
%*{"id":5,"name":"user5","email":"user5@example.com","address":newJNull(),"submit_on":"2020-05-01","submit_at":"2020-05-01 00:00:00","auth_id":1},
%*{"id":7,"name":"user7","email":"user7@example.com","address":newJNull(),"submit_on":"2020-07-01","submit_at":"2020-07-01 00:00:00","auth_id":1},
%*{"id":9,"name":"user9","email":"user9@example.com","address":newJNull(),"submit_on":"2020-09-01","submit_at":"2020-09-01 00:00:00","auth_id":1}
]
test("whereBetween"):
var t = rdb
.select("id", "name")
.table("user")
.where("auth_id", "=", 1)
.whereBetween("id", [6, 9])
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user7"
test("whereBetweenString"):
var t = rdb
.table("user")
.whereBetween("submit_at", ["2020-01-01 00:00:00", "2020-01-31 00:00:00"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
t = rdb
.table("user")
.whereBetween("submit_on", ["2020-01-01", "2020-01-31"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
test("whereNotBetween"):
var t = rdb
.select("id", "name")
.table("user")
.where("auth_id", "=", 1)
.whereNotBetween("id", [1, 4])
.get()
.waitFor
check t[0]["name"].getStr() == "user5"
test("whereNotBetweenString"):
var t = rdb
.table("user")
.whereNotBetween("submit_at", ["2020-2-1 00:00:00", "2020-12-31 00:00:00"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
t = rdb
.table("user")
.whereNotBetween("submit_on", ["2020-2-1", "2020-12-31"])
.get()
.waitFor
check t[0]["name"].getStr == "user1"
test("whereIn"):
var t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereIn("id", @[5, 6, 7])
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user5"
t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereIn("name", @["user5", "user6", "user7"])
.get()
.waitFor
check t[0]["name"].getStr() == "user5"
test("whereNotIn"):
var t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereNotIn("id", @[5, 6, 7])
.get()
.waitFor
echo t
check t == @[
%*{"id":4, "name": "user4"},
%*{"id":8, "name": "user8"},
%*{"id":9, "name": "user9"},
%*{"id":10, "name": "user10"},
]
t = rdb
.select("id", "name")
.table("user")
.whereBetween("id", [4, 10])
.whereNotIn("name", @["user5", "user6", "user7"])
.get()
.waitFor
echo t
check t == @[
%*{"id":4, "name": "user4"},
%*{"id":8, "name": "user8"},
%*{"id":9, "name": "user9"},
%*{"id":10, "name": "user10"},
]
test("whereNull"):
setUp(rdb)
rdb.table("user").insert(%*{"email": "user11@example.com"}).waitFor
var t = rdb
.select("id", "name", "email")
.table("user")
.whereNull("name")
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 11
test("groupBy"):
var t = rdb
.table("user")
.groupBy("auth_id")
.max("id")
.waitFor()
.get()
echo t
check t == "11"
test("having"):
var t = rdb
.select("id", "name")
.table("user")
.groupBy("auth_id")
.groupBy("id")
.having("auth_id", "=", 1)
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 1
test("orderBy"):
var t = rdb.table("user")
.where("auth_id", "is not", nil)
.orderBy("auth_id", Asc)
.orderBy("id", Desc)
.get()
.waitFor
echo t
check t[0]["id"].getInt() == 9
test("join"):
var t = rdb
.select("user.id", "user.name")
.table("user")
.join("auth", "auth.id", "=", "user.auth_id")
.where("auth.id", "=", "2")
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user2"
test("leftJoin"):
rdb.table("user").insert(%*{
"name": "user11"
})
.waitFor
var t = rdb
.select("user.id", "user.name", "user.auth_id")
.table("user")
.leftJoin("auth", "auth.id", "=", "user.auth_id")
.orderBy("user.id", Desc)
.get()
.waitFor
echo t
check t[0]["name"].getStr() == "user11"
check t[0]["auth_id"] == newJNull()
test("resultIsNull"):
check rdb.table("user").find(50).waitFor().isSome == false
check newSeq[JsonNode](0) == rdb.table("user").where("id", "=", 50).get().waitFor
suite($rdb & " insert"):
setup:
setup(rdb)
test("insert row"):
rdb.table("user").insert(%*{"name": "Alice"}).waitFor
let t = rdb.table("user").orderBy("id", Desc).first().waitFor().get()
check t["name"].getStr() == "Alice"
test("insert rows"):
rdb.table("user").insert(@[
%*{"name": "Alice"},
%*{"name": "Bob"},
]).waitFor
let t = rdb.table("user").orderBy("id", Desc).limit(2).get().waitFor()
check t[0]["name"].getStr() == "Bob"
check t[1]["name"].getStr() == "Alice"
test("insertId row"):
var id = rdb.table("user").insertId(%*{"name": "Alice"}).waitFor
echo id
let t = rdb.table("user").find(id).waitFor().get()
check t["name"].getStr() == "Alice"
test("insertId rows"):
let ids = rdb.table("user").insertId(@[
%*{"name": "Alice"},
%*{"name": "Bob"},
]).waitFor
var t = rdb.table("user").find(ids[0]).waitFor().get()
check t["name"].getStr == "Alice"
t = rdb.table("user").find(ids[1]).waitFor().get()
check t["name"].getStr == "Bob"
test("insertNil"):
var id = rdb.table("user").insertId(%*{
"name": "Alice",
"email": nil,
"address": ""
})
.waitFor
var res = rdb.table("user").find(id).waitFor
echo res.get
check res.get["email"] == newJNull()
res = rdb.table("user").where("email", "is", nil).first().waitFor
echo res.get
check res.get["email"] == newJNull()
test("insert [T]"):
type User = object
name:string
let user = User(name: "Alice")
rdb.table("user").insert(user).waitFor
let row = rdb.table("user").orderBy("id", Desc).first().waitFor().get()
check row["name"].getStr() == "Alice"
test("insert seq[T]"):
type User = object
name:string
let users = @[
User(name: "Alice"),
User(name: "Bob"),
]
rdb.table("user").insert(users).waitFor
let t = rdb.table("user").orderBy("id", Desc).limit(2).get().waitFor()
check t[0]["name"].getStr() == "Bob"
check t[1]["name"].getStr() == "Alice"
suite($rdb & " update"):
setup:
setup(rdb)
test("update"):
rdb.table("user").where("id", "=", 1).update(%*{"name": "Alice"}).waitFor
var t = rdb.table("user").find(1).waitFor().get()
check t["name"].getStr() == "Alice"
test("update [T]"):
type User = object
name:string
let user = User(name: "Alice")
rdb.table("user").where("id", "=", 1).update(user).waitFor
var t = rdb.table("user").find(1).waitFor().get()
check t["name"].getStr() == "Alice"
suite($rdb & " delete"):
setup(rdb)
test("delete"):
rdb.table("user").delete(1).waitFor
check rdb.table("user").find(1).waitFor.isSome == false
test("delete where"):
rdb.table("user").where("name", "=", "user1").delete().waitFor
check rdb.table("user").find(1).waitFor.isSome == false
suite($rdb & " rawQuery"):
setup(rdb)
test("get"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT * FROM \"user\" WHERE \"id\" = ?"
let res = rdb.raw(sql, %*[1]).get().waitFor
echo res
check res[0]["name"].getStr == "user1"
test("getPlain"):
let sql = &"SELECT * FROM \"user\" WHERE \"id\" = ?"
let res = rdb.raw(sql, %*[1]).getPlain().waitFor
echo res
check res[0][1] == "user1"
test("first"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT * FROM \"user\" WHERE \"id\" = ?"
let res = rdb.raw(sql, %*[1]).first().waitFor().get()
echo res
check res["name"].getStr == "user1"
test("firstPlain"):
echo rdb.table("user").get().waitFor
let sql = &"SELECT name FROM \"user\" WHERE \"id\" = ?"
let res = rdb.raw(sql, %*[1]).firstPlain().waitFor()
echo res
check res[0] == "user1"
test("exec"):
var sql = "UPDATE \"user\" SET \"name\" = ? WHERE \"id\" = ?"
rdb.raw(sql, %*["updated user1", 1]).exec().waitFor
sql = &"SELECT * FROM \"user\" WHERE \"id\" = ?"
let res = rdb.raw(sql, %[1]).get().waitFor
echo res
check res[0]["name"].getStr == "updated user1"
setup(rdb)
suite($rdb & " aggregates"):
test("count"):
var t = rdb.table("user").count().waitFor
check t == 10
test("max"):
var t = rdb.table("user").max("name").waitFor.get
check t == "user9"
var t2 = rdb.table("user").max("id").waitFor.get
check t2 == "10"
test("min"):
var t = rdb.table("user").min("name").waitFor.get
check t == "user1"
var t2 = rdb.table("user").min("id").waitFor.get
check t2 == "1"
test("avg"):
var t = rdb.table("user").avg("id").waitFor.get
check t == 5.5
test("sum"):
var t = rdb.table("user").sum("id").waitFor.get
check t == 55.0
suite($rdb & " insert binary"):
test("should success"):
rdb.create(
table("test", [
Column.increments("id"),
Column.binary("pic"),
])
)
let client = newAsyncHttpClient()
let response = client.getContent("https://nim-lang.org/assets/img/twitter_banner.png").waitFor()
let imageStream = newStringStream(response)
let binaryImage = imageStream.readAll()
let id = rdb.table("test").insertId(%*{"pic": binaryImage}).waitFor()
var res = rdb.table("test").find(id).waitFor().get()
check res["pic"].getStr().len > 0
rdb.table("test").where("id","=", id).update(%*{"pic": binaryImage}).waitFor()
res = rdb.table("test").find(id).waitFor().get()
check res["pic"].getStr().len > 0
clearTables(rdb).waitFor()
@@ -0,0 +1,298 @@
discard """
cmd: "nim c -d:reset $file"
"""
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = sqlite
suite("Sqlite create table"):
test("create table"):
rdb.create(
table("IntRelation", [
Column.increments("id")
]),
table("StrRelation", [
Column.uuid("uuid")
]),
table("TypeIndex", [
Column.increments("id"),
Column.integer("integer").unsigned().index().default(1),
Column.smallInteger("smallInteger").unsigned().index().default(1),
Column.mediumInteger("mediumInteger").unsigned().index().default(1),
Column.bigInteger("bigInteger").unsigned().index().default(1),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1),
Column.double("double", 10, 3).unsigned().index().default(1.1),
Column.float("float").unsigned().index().default(1.1),
Column.uuid("uuid").index().default("A"),
Column.char("char", 255).index().default("A"),
Column.string("string").index().default("A"),
Column.text("text").index().default("A"),
Column.mediumText("mediumText").index().default("A"),
Column.longText("longText").index().default("A"),
Column.date("date").index().default(),
Column.datetime("datetime").index().default(),
Column.time("time").index().default(),
Column.timestamp("timestamp").index().default(),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary").index().default("A"),
Column.boolean("boolean").index().default(true),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A"),
Column.json("json").index().default(%*{"key":"value"}),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL)
]),
table("TypeUnique", [
Column.increments("id"),
Column.integer("integer").unsigned().unique().index().default(1),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1),
Column.float("float").unsigned().unique().index().default(1.1),
Column.uuid("uuid").unique().index().default("A"),
Column.char("char", 255).unique().index().default("A"),
Column.string("string").unique().index().default("A"),
Column.text("text").unique().index().default("A"),
Column.mediumText("mediumText").unique().index().default("A"),
Column.longText("longText").unique().index().default("A"),
Column.date("date").unique().index().default(),
Column.datetime("datetime").unique().index().default(),
Column.time("time").unique().index().default(),
Column.timestamp("timestamp").unique().index().default(),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary").unique().index().default("A"),
Column.boolean("boolean").unique().index().default(true),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A"),
Column.json("json").unique().index().default(%*{"key":"value"}),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL)
])
)
test("change columns"):
rdb.alter(
table("TypeIndex", [
Column.integer("integer").unsigned().index().default(1).change(),
Column.smallInteger("smallInteger").unsigned().index().default(1).change(),
Column.mediumInteger("mediumInteger").unsigned().index().default(1).change(),
Column.bigInteger("bigInteger").unsigned().index().default(1).change(),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1).change(),
Column.double("double", 10, 3).unsigned().index().default(1.1).change(),
Column.float("float").unsigned().index().default(1.1).change(),
Column.uuid("uuid").index().default("A").change(),
Column.char("char", 255).index().default("A").change(),
Column.string("string").index().default("A").change(),
Column.text("text").index().default("A").change(),
Column.mediumText("mediumText").index().default("A").change(),
Column.longText("longText").index().default("A").change(),
Column.date("date").index().default().change(),
Column.datetime("datetime").index().default().change(),
Column.time("time").index().default().change(),
Column.timestamp("timestamp").index().default().change(),
Column.binary("binary").index().default("A").change(),
Column.boolean("boolean").index().default(true).change(),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A").change(),
Column.json("json").index().default(%*{"key":"value"}).change(),
]),
table("TypeUnique", [
Column.integer("integer").unsigned().unique().index().default(1).change(),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1).change(),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1).change(),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1).change(),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1).change(),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1).change(),
Column.float("float").unsigned().unique().index().default(1.1).change(),
Column.uuid("uuid").unique().index().default("A").change(),
Column.char("char", 255).unique().index().default("A").change(),
Column.string("string").unique().index().default("A").change(),
Column.text("text").unique().index().default("A").change(),
Column.mediumText("mediumText").unique().index().default("A").change(),
Column.longText("longText").unique().index().default("A").change(),
Column.date("date").unique().index().default().change(),
Column.datetime("datetime").unique().index().default().change(),
Column.time("time").unique().index().default().change(),
Column.timestamp("timestamp").unique().index().default().change(),
Column.binary("binary").unique().index().default("A").change(),
Column.boolean("boolean").unique().index().default(true).change(),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A").change(),
Column.json("json").unique().index().default(%*{"key":"value"}).change(),
])
)
suite($rdb & " alter table"):
setup:
rdb.create(
table("IntRelation", [
Column.increments("id"),
]),
table("StrRelation", [
Column.uuid("uuid"),
]),
table("TypeUnique", [
Column.integer("num"),
Column.string("str"),
]),
table("TypeIndex", [
Column.integer("num"),
Column.string("str"),
])
)
test("add columns"):
rdb.alter(
table("TypeIndex", [
Column.increments("id").add(),
Column.integer("integer").unsigned().index().default(1).add(),
Column.smallInteger("smallInteger").unsigned().index().default(1).add(),
Column.mediumInteger("mediumInteger").unsigned().index().default(1).add(),
Column.bigInteger("bigInteger").unsigned().index().default(1).add(),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1).add(),
Column.double("double", 10, 3).unsigned().index().default(1.1).add(),
Column.float("float").unsigned().index().default(1.1).add(),
Column.uuid("uuid").index().default("A").add(),
Column.char("char", 255).index().default("A").add(),
Column.string("string").index().default("A").add(),
Column.text("text").index().default("A").add(),
Column.mediumText("mediumText").index().default("A").add(),
Column.longText("longText").index().default("A").add(),
Column.date("date").index().default().add(),
Column.datetime("datetime").index().default(),
Column.time("time").index().default().add(),
Column.timestamp("timestamp").index().default().add(),
Column.timestamps().add(),
Column.softDelete().add(),
Column.binary("binary").index().default("A").add(),
Column.boolean("boolean").index().default(true).add(),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A").add(),
Column.json("json").index().default(%*{"key":"value"}).add(),
Column.foreign("int_relation_id").reference("id").onTable("IntRelation").onDelete(SET_NULL).add(),
Column.strForeign("str_relation_id").reference("uuid").onTable("StrRelation").onDelete(SET_NULL).add()
]),
table("TypeUnique", [
Column.increments("id").add(),
Column.integer("integer").unsigned().unique().index().default(1).add(),
Column.smallInteger("smallInteger").unsigned().unique().index().default(1).add(),
Column.mediumInteger("mediumInteger").unsigned().unique().index().default(1).add(),
Column.bigInteger("bigInteger").unsigned().unique().index().default(1).add(),
Column.decimal("decimal", 10, 3).unsigned().unique().index().default(1.1).add(),
Column.double("double", 10, 3).unsigned().unique().index().default(1.1).add(),
Column.float("float").unsigned().unique().index().default(1.1).add(),
Column.uuid("uuid").unique().index().default("A").add(),
Column.char("char", 255).unique().index().default("A").add(),
Column.string("string").unique().index().default("A").add(),
Column.text("text").unique().index().default("A").add(),
Column.mediumText("mediumText").unique().index().default("A").add(),
Column.longText("longText").unique().index().default("A").add(),
Column.date("date").unique().index().default().add(),
Column.datetime("datetime").unique().index().default().add(),
Column.time("time").unique().index().default().add(),
Column.timestamp("timestamp").unique().index().default().add(),
Column.timestamps().add(),
Column.softDelete().add(),
Column.binary("binary").unique().index().default("A").add(),
Column.boolean("boolean").unique().index().default(true).add(),
Column.enumField("enumField", ["A", "B", "C"]).unique().index().default("A").add(),
Column.json("json").unique().index().default(%*{"key":"value"}).add()
])
)
test("rename column"):
rdb.alter(
table("TypeIndex", [
Column.renameColumn("num", "num2"),
Column.renameColumn("str", "str2"),
])
)
let columns = rdb.table("TypeIndex").columns().waitFor
check not columns.contains("num")
check columns.contains("num2")
check not columns.contains("str")
check columns.contains("str2")
test("drop column"):
rdb.alter(
table("TypeIndex", [
Column.dropColumn("str")
])
)
let columns = rdb.table("TypeIndex").columns().waitFor
check not columns.contains("str")
test("rename table"):
rdb.drop(
table("TypeIndex_renamed")
)
rdb.table("TypeIndex").insert(%*{"num":1, "str": "a"}).waitFor
rdb.alter(
table("TypeIndex").renameTo("TypeIndex_renamed")
)
var res:Option[JsonNode]
try:
res = rdb.table("TypeIndex").first().waitFor
check not res.isSome
except CatchableError:
check true
res = rdb.table("TypeIndex_renamed").first().waitFor
check res.isSome
test("drop table"):
rdb.create(
table("TypeIndex", [
Column.integer("num")
])
)
rdb.drop(
table("TypeIndex")
)
var res:Option[JsonNode]
try:
res = rdb.table("TypeIndex").first().waitFor
check not res.isSome
except CatchableError:
check true
suite($rdb & " primary"):
test("primary key"):
rdb.create(
table("relation", [
Column.increments("id"),
]),
table("TypeIndex", @[
Column.integer("index1"),
Column.integer("index2"),
Column.string("string"),
Column.foreign("relation_id").reference("id").onTable("relation").onDelete(SET_NULL)
],
primary = @["index1", "index2"]
)
)
clearTables(rdb).waitFor()
@@ -0,0 +1,88 @@
discard """
cmd: "nim c -d:reset $file"
"""
# nim c -r -d:reset tests/sqlite/test_schema_length_check.nim
import std/unittest
import std/asyncdispatch
import std/json
import std/strformat
import std/strutils
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = sqlite
proc tableSql(tableName:string): string =
let rows = rdb.raw(&"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = '{tableName}'").get().waitFor
return rows[0]["sql"].getStr
suite("Sqlite length check constraint"):
test("create table uses a column reference in CHECK"):
let tableName = "length_check_create"
rdb.create(
table(tableName, [
Column.increments("id"),
Column.char("char", 4).default("ABCD"),
Column.string("string", 4).nullable(),
Column.uuid("uuid").nullable()
])
)
let sql = tableSql(tableName)
check sql.contains("DEFAULT 'ABCD'")
check sql.contains("CHECK (length(\"char\") <= 4)")
check sql.contains("CHECK (length(\"string\") <= 4)")
check sql.contains("CHECK (length(\"uuid\") <= 256)")
expect DbError:
rdb.table(tableName).insert(@[
%*{
"char": "ABCDE",
"string": "ABCDE",
"uuid": repeat("u", 300)
}
]).waitFor
test("add column keeps CHECK on a column reference"):
let tableName = "length_check_add"
rdb.create(
table(tableName, [
Column.increments("id")
])
)
rdb.alter(
table(tableName, [
Column.char("char", 4).nullable().add(),
Column.string("string", 4).default("WXYZ").add(),
Column.uuid("uuid").default("ZZZZ").add()
])
)
let sql = tableSql(tableName)
check sql.contains("CHECK (length(\"char\") <= 4)")
check sql.contains("CHECK (length(\"string\") <= 4)")
check sql.contains("CHECK (length(\"uuid\") <= 256)")
check sql.contains("DEFAULT 'WXYZ'")
check sql.contains("DEFAULT 'ZZZZ'")
expect DbError:
rdb.table(tableName).insert(@[
%*{
"char": "ABCDE",
"string": "ABCDE",
"uuid": repeat("u", 300)
}
]).waitFor
clearTables(rdb).waitFor
@@ -0,0 +1,128 @@
discard """
cmd: "nim c --mm:orc -d:reset -r $file"
"""
import std/unittest
import std/json
import std/strformat
import std/strutils
import std/options
import std/asyncdispatch
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = sqlite
proc setUp(rdb:SqliteConnections) =
rdb.create([
table("auth",[
Column.increments("id"),
Column.string("auth")
]),
table("user",[
Column.increments("id"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.foreign("auth_id").reference("id").onTable("auth").onDelete(SET_NULL)
])
])
# seeder
seeder(rdb, "auth"):
rdb.table("auth").insert(@[
%*{"auth": "admin"},
%*{"auth": "user"}
])
.waitFor()
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: 2 else: 1
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"auth_id": authId
}
)
rdb.table("user").insert(users).waitFor()
suite("transaction"):
suite("raw code"):
setup:
setUp(rdb)
test("commit"):
(proc() {.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
block:
rdb.begin().await
try:
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
rdb.commit().await
except DbError, CatchableError:
rdb.rollback().await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "updated"
)().waitFor
test("rollback"):
(proc() {.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
block:
rdb.begin().await
try:
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
raise newException(CatchableError, "")
except DbError, CatchableError:
rdb.rollback().await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
)().waitFor
suite("transaction template"):
setup:
setUp(rdb)
test("commit"):
(proc(){.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
transaction(rdb):
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "updated"
)().waitFor()
test("rollback"):
(proc(){.async.} =
var user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
transaction(rdb):
rdb.table("user").where("id", "=", 1).update(%*{"name": "updated"}).await
raise newException(CatchableError, "")
user1 = rdb.table("user").find(1).await.get()
check user1["name"].getStr() == "user1"
)().waitFor()
clearTables(rdb).waitFor()
@@ -0,0 +1,27 @@
import std/asyncdispatch
import std/os
import std/strutils
import ../../src/allographer/connection
proc envStringDefault(key, defaultValue: string): string =
let value = getEnv(key)
if value.len == 0:
return defaultValue
return value
proc envIntDefault(key: string, defaultValue: int): int =
let value = getEnv(key)
if value.len == 0:
return defaultValue
return value.parseInt
let
database = envStringDefault("DB_DATABASE", "test")
user = envStringDefault("DB_USER", "user")
password = envStringDefault("DB_PASSWORD", "pass")
surrealHost = envStringDefault("SURREAL_HOST", "http://surreal")
surrealPort = envIntDefault("SURREAL_PORT", 8000)
maxConnections = envIntDefault("DB_MAX_CONNECTION", 5)
timeout = envIntDefault("DB_TIMEOUT", 30)
let surreal* = dbOpen(SurrealDB, database, database, user, password, surrealHost, surrealPort, maxConnections, timeout, shouldDisplayLog=true).waitFor()
@@ -0,0 +1,16 @@
import std/asyncdispatch
import std/os
import std/strutils
import ../../src/allographer/connection
proc envIntDefault(key: string, defaultValue: int): int =
let value = getEnv(key)
if value.len == 0:
return defaultValue
return value.parseInt
let
maxConnections = envIntDefault("DB_MAX_CONNECTION", 5)
timeout = envIntDefault("DB_TIMEOUT", 30)
let surreal* = dbOpen(SurrealDB, "test", "test", "user", "pass", "http://surreal", 8000, maxConnections, timeout, shouldDisplayLog=true).waitFor()
@@ -0,0 +1,97 @@
discard """
cmd: "nim c -d:reset -d:ssl $file"
"""
# nim c -r -d:reset -d:ssl tests/surrealdb/test_autoincrements.nim
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import std/strformat
import ../../src/allographer/query_builder
import ../../src/allographer/schema_builder
import ./connections
import ../clear_tables
let rdb = surreal
suite("autoincrements"):
test("sequence table is not exists"):
rdb.raw("REMOVE TABLE _autoincrement_sequences").exec().waitFor()
var info = rdb.raw("INFO FOR DB").info().waitFor()
echo "info: ", info.pretty
check not info[0]["result"]["tables"].contains("_autoincrement_sequences")
rdb.create(
table("user", [
Column.increments("index"),
Column.string("name")
])
)
info = rdb.raw("INFO FOR DB").info().waitFor()
echo "info: ", info.pretty
check info[0]["result"]["tables"].contains("_autoincrement_sequences")
let id = rdb.table("user").insertId(%*{"name": "alice"}).waitFor()
var user = rdb.table("user").find(id).waitFor().get()
check user["name"].getStr == "alice"
check user["index"].getInt == 1
rdb.update(id, %*{"name": "updated"}).waitFor()
user = rdb.table("user").find(id).waitFor().get()
check user["name"].getStr == "updated"
check user["index"].getInt == 1
test("sequence table is exists"):
var info = rdb.raw("INFO FOR DB").info().waitFor()
check info[0]["result"]["tables"].contains("_autoincrement_sequences")
rdb.create(
table("user", [
Column.increments("index"),
Column.string("name")
])
)
let id = rdb.table("user").insertId(%*{"name": "alice"}).waitFor()
var user = rdb.table("user").find(id).waitFor().get()
check user["name"].getStr == "alice"
check user["index"].getInt == 1
rdb.update(id, %*{"name": "updated"}).waitFor()
user = rdb.table("user").find(id).waitFor().get()
check user["name"].getStr == "updated"
check user["index"].getInt == 1
test("cycle"):
rdb.create(
table("user", [
Column.increments("index"),
Column.string("name")
])
)
var users: seq[JsonNode]
for i in 1..100:
users.add(%*{"name": &"user{i}"})
rdb.table("user").insert(users).waitFor()
var user = rdb.table("user").where("index", "=", 1).first().waitFor().get()
check user["index"].getInt == 1
user = rdb.table("user").where("index", "=", 50).first().waitFor().get()
check user["index"].getInt == 50
user = rdb.table("user").where("index", "=", 100).first().waitFor().get()
check user["index"].getInt == 100
echo rdb.table("user").orderBy("index", Asc).limit(20).get().waitFor()
clearTables(rdb).waitFor()
@@ -0,0 +1,106 @@
discard """
cmd: "nim c -d:reset $file"
"""
# nim c -d:reset -r tests/surrealdb/test_create_schema.nim
import std/unittest
import std/asyncdispatch
import std/os
import std/strutils
import std/json
import ../../src/allographer/schema_builder
import ./connections
import ../clear_tables
let rdb = surreal
let schemaFilePath = getCurrentDir() / "schema.nim"
clearTables(rdb).waitFor()
suite "Schema output after migration":
setup:
# schema.nim ファイルの削除
if fileExists(schemaFilePath):
removeFile(schemaFilePath)
rdb.create(
table("int_relation", [
Column.increments("index")
]),
table("str_relation", [
Column.uuid("uuid")
]),
table("test_schema_output", [
Column.increments("index"),
Column.integer("integer"),
Column.smallInteger("smallInteger"),
Column.mediumInteger("mediumInteger"),
Column.bigInteger("bigInteger"),
Column.decimal("decimal", 10, 3),
Column.double("double", 10, 3),
Column.float("float"),
Column.uuid("uuid"),
Column.char("char", 255),
Column.string("string"),
Column.text("text"),
Column.mediumText("mediumText"),
Column.longText("longText"),
Column.date("date"),
Column.datetime("datetime"),
Column.timestamp("timestamp"),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary"),
Column.boolean("boolean"),
Column.enumField("enumField", ["A", "B", "C"]),
Column.json("json"),
Column.foreign("int_relation_id").reference("id").onTable("int_relation").onDelete(SET_NULL),
Column.strForeign("str_relation_id").reference("uuid").onTable("str_relation").onDelete(SET_NULL)
])
)
test "should generate schema.nim file":
# スキーマ生成
rdb.createSchema(schemaFilePath).waitFor()
# schema.nim ファイルの存在確認
check fileExists(schemaFilePath)
# ファイル内容の検証
let schemaContent = readFile(schemaFilePath)
echo "schemaContent: ", schemaContent
check schemaContent.contains("type TestSchemaOutputTable* = object")
check schemaContent.contains("## test_schema_output")
check schemaContent.contains("integer*: int")
check schemaContent.contains("smallInteger*: int")
check schemaContent.contains("mediumInteger*: int")
check schemaContent.contains("bigInteger*: int")
check schemaContent.contains("decimal*: float")
check schemaContent.contains("double*: float")
check schemaContent.contains("float*: float")
check schemaContent.contains("uuid*: string")
check schemaContent.contains("char*: string")
check schemaContent.contains("string*: string")
check schemaContent.contains("text*: string")
check schemaContent.contains("mediumText*: string")
check schemaContent.contains("longText*: string")
check schemaContent.contains("date*: string")
check schemaContent.contains("datetime*: string")
check schemaContent.contains("timestamp*: string")
check schemaContent.contains("created_at*: string")
check schemaContent.contains("updated_at*: string")
check schemaContent.contains("deleted_at*: string")
check schemaContent.contains("binary*: string")
check schemaContent.contains("boolean*: bool")
check schemaContent.contains("enumField*: string")
check schemaContent.contains("json*: JsonNode")
check schemaContent.contains("int_relation_id*: string")
check schemaContent.contains("str_relation_id*: string")
clearTables(rdb).waitFor()
# schema.nim ファイルの削除
if fileExists(schemaFilePath):
removeFile(schemaFilePath)
@@ -0,0 +1,26 @@
discard """
cmd: "nim c -d:reset $file"
"""
import std/json
import std/unittest
import ../../src/allographer/query_builder/libs/surreal/surreal_lib
suite "SurrealDB lib helpers":
test "numToAlphabet":
check numToAlphabet(1) == "a"
check numToAlphabet(26) == "z"
check numToAlphabet(27) == "aa"
test "questionToDaller":
check questionToDaller("SELECT * FROM user WHERE name = ? AND age = ?") ==
"SELECT * FROM user WHERE name = $a AND age = $b"
test "dbFormat json args":
let sql = dbFormat(
"SELECT * FROM user WHERE name = ? AND age = ?",
%*["alice", 42]
)
check sql ==
"LET $a = \"alice\"; LET $b = 42; SELECT * FROM user WHERE name = $a AND age = $b"
@@ -0,0 +1,25 @@
discard """
cmd: "nim c -d:reset $file"
"""
import std/[asyncdispatch, os, strutils, unittest]
import ../../src/allographer/connection
import ../../src/allographer/query_builder
proc envIntDefault(key: string, defaultValue: int): int =
let value = getEnv(key)
if value.len == 0:
return defaultValue
return value.parseInt
suite "SurrealDB pool waiter (notify)":
test "pool size 1: two concurrent raw gets both complete":
let timeout = envIntDefault("DB_TIMEOUT", 30)
let rdb = dbOpen(SurrealDB, "test", "test", "user", "pass", "http://surreal", 8000,
maxConnections = 1, timeout).waitFor()
proc sel(): Future[void] {.async.} =
discard await rdb.raw("RETURN 1;").get()
let a = sel()
let b = sel()
waitFor all(a, b)
@@ -0,0 +1,98 @@
discard """
cmd: "nim c -d:reset $file"
"""
import std/asyncdispatch
import std/deques
import std/json
import std/tables
import std/unittest
import ../../src/allographer/query_builder/libs/surreal/surreal_lib
import ../../src/allographer/query_builder/libs/surreal/surreal_rdb
import ../../src/allographer/query_builder/log
import ../../src/allographer/query_builder/models/surreal/surreal_exec
import ../../src/allographer/query_builder/models/surreal/surreal_types
proc newTestSurreal(): SurrealConnections =
let pools = Connections(
conns: @[
Connection(
conn: SurrealConn(),
isBusy: false,
createdAt: 0,
)
],
timeout: 30,
waiters: initDeque[Future[void]](),
preparedCache: initTable[string, SurrealPreparedEntry](),
)
result = SurrealConnections(
log: LogSetting(
shouldDisplayLog: false,
shouldOutputLogFile: false,
logDir: "",
),
pools: pools,
)
suite "SurrealDB prepared statement":
test "dbFormatPrepared":
var args = newJArray()
args.add(%*"user:alice")
args.add(%*"2026-04-03T00:00:00Z")
args.add(newJNull())
let sql = dbFormatPrepared(
questionToDaller("""SELECT * FROM "user" WHERE "id" = ? AND "submit_at" >= ? AND "address" IS ?"""),
args
)
check sql ==
"""LET $a = user:alice; LET $b = <datetime>"2026-04-03T00:00:00Z"; LET $c = NONE; SELECT * FROM "user" WHERE "id" = $a AND "submit_at" >= $b AND "address" IS $c"""
test "prepare cache lifecycle":
let rdb = newTestSurreal()
let sql = """SELECT * FROM "user" WHERE "id" = ?"""
let stmt1 = rdb.prepare(sql)
check stmt1.nArgs == 1
check stmt1.entry.normalizedSql == questionToDaller(sql)
check stmt1.entry.refCount == 1
check rdb.pools.preparedCache.len == 1
let stmt2 = rdb.prepare(sql)
check stmt2.entry == stmt1.entry
check stmt1.entry.refCount == 2
waitFor stmt1.close()
check stmt1.isClosed
check stmt1.entry.refCount == 1
waitFor stmt2.close()
check stmt2.isClosed
check stmt2.entry.refCount == 0
let stmt4 = rdb.prepare(sql)
check rdb.pools.preparedCache.len == 1
waitFor rdb.flushStmt(stmt4)
check stmt4.isClosed
check rdb.pools.preparedCache.len == 0
let stmt3 = rdb.prepare(sql)
check stmt3.entry != stmt1.entry
waitFor stmt3.close()
waitFor rdb.clearStmtCache()
check rdb.pools.preparedCache.len == 0
test "withConn context":
let rdb = newTestSurreal()
waitFor rdb.withConn(
proc(ctx: SurrealPreparedContext): Future[void] {.async.} =
check ctx.owner == rdb
check ctx.connI == 0
)
check not rdb.pools.conns[0].isBusy
@@ -0,0 +1,507 @@
discard """
cmd: "nim c -d:reset $file"
"""
# nim c -r -d:reset tests/surrealdb/test_query.nim
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import std/strformat
import std/times
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
let rdb = surreal
proc isNullish(node: JsonNode, key: string): bool =
not node.hasKey(key) or node[key].kind == JNull
# =============================================================================
# test
# =============================================================================
proc setup(rdb:SurrealConnections) =
# rdb.raw("REMOVE TABLE auth; REMOVE TABLE user").exec().waitFor
# rdb.raw("DEFINE TABLE auth SCHEMAFULL; DEFINE TABLE user SCHEMAFULL").exec().waitFor
rdb.create([
table("auth",[
Column.increments("index"),
Column.string("name")
]),
table("user",[
Column.increments("index"),
Column.string("name").nullable(),
Column.string("email").nullable(),
Column.string("address").nullable(),
Column.date("submit_on"),
Column.datetime("submit_at"),
Column.foreign("auth").reference("id").onTable("auth").onDelete(SET_NULL).nullable()
])
])
# seeder
var adminId, userId:SurrealId
seeder(rdb, "auth"):
adminId = rdb.table("auth").insertId(%*{"name":"admin"}).waitFor()
userId = rdb.table("auth").insertId(%*{"name":"user"}).waitFor()
seeder(rdb, "user"):
var users: seq[JsonNode]
for i in 1..10:
let authId = if i mod 2 == 0: userId else: adminId
let month = if i > 9: $i else: &"0{i}"
users.add(
%*{
"name": &"user{i}",
"email": &"user{i}@example.com",
"address": newJNull(),
"auth": authId,
"submit_on": &"2020-{month}-01",
"submit_at": &"2020-{month}-01T00:00:00Z",
}
)
rdb.table("user").insert(users).waitFor()
setup(rdb)
suite($rdb & " get"):
test("get"):
let t = rdb.table("user").orderBy("name", Asc).get().waitFor()
echo t[0]
check t[0]["index"].getInt() == 1
check t[0]["name"].getStr() == "user1"
check t[0]["email"].getStr() == "user1@example.com"
check isNullish(t[0], "address")
check t[0]["submit_on"].getStr().parse("yyyy-MM-dd'T'hh:mm:ss'Z'").format("yyyy-MM-dd") == "2020-01-01"
check t[0]["submit_at"].getStr() == "2020-01-01T00:00:00Z"
test("first"):
var t = rdb.table("user").where("name", "=", "user1").orderBy("name", Asc).first().waitFor().get()
check t["index"].getInt() == 1
check t["name"].getStr() == "user1"
check t["email"].getStr() == "user1@example.com"
check isNullish(t, "address")
check t["submit_on"].getStr().parse("yyyy-MM-dd'T'hh:mm:ss'Z'").format("yyyy-MM-dd") == "2020-01-01"
check t["submit_at"].getStr() == "2020-01-01T00:00:00Z"
test("find"):
var t = rdb.table("user").where("name", "=", "user1").orderBy("name", Asc).first().waitFor().get()
let id = SurrealId.new(t["id"].getStr())
t = rdb.table("user").find(id).waitFor().get()
check t["index"].getInt() == 1
check t["name"].getStr() == "user1"
check t["email"].getStr() == "user1@example.com"
check isNullish(t, "address")
check t["submit_on"].getStr().parse("yyyy-MM-dd'T'hh:mm:ss'Z'").format("yyyy-MM-dd") == "2020-01-01"
check t["submit_at"].getStr() == "2020-01-01T00:00:00Z"
test("columns"):
let columns = rdb.table("user").columns().waitFor()
check columns == @["address", "auth", "email", "index", "name", "submit_at", "submit_on"]
test("select"):
var t = rdb.select("name", "email").table("user").orderBy("name", Asc).get().waitFor()
check t[0] == %*{"name": "user1", "email": "user1@example.com"}
test("selectAs"):
var t = rdb.select("name as user_name", "email").table("user").orderBy("user_name", Asc).get().waitFor()
check t[0] == %*{"user_name": "user1", "email": "user1@example.com"}
test("selectLike"):
let users = rdb.table("user").where("email", "CONTAINS", "10").get().waitFor()
check users[0]["email"].getStr() == "user10@example.com"
var user10 = rdb.raw("SELECT * FROM user WHERE string::starts_with(email, \"user10\")").first().waitFor()
check user10.get()["email"].getStr() == "user10@example.com"
user10 = rdb.raw("SELECT * FROM user WHERE string::ends_with(email, \"10@example.com\")").first().waitFor()
check user10.get()["email"].getStr() == "user10@example.com"
test("select count"):
var t = rdb.raw("SELECT count(`id`) as `count` FROM `user` GROUP ALL").get().waitFor
echo t
check t[0]["count"].getInt() == 10
test("select max"):
var t = rdb.raw("SELECT math::max(`index`) as `max` FROM `user` GROUP ALL").get().waitFor
echo t
check t[0]["max"].getInt() == 10
test("where string"):
let admin = rdb.table("auth").where("name", "=", "admin").first().waitFor().get()
let adminId = SurrealId.new(admin["id"].getStr())
var t = rdb.table("user").where("auth", "=", adminId.rawId()).orderBy("index", Asc).get().waitFor()
const ids = [1,3,5,7,9]
for i, row in t:
check row["index"].getInt() == ids[i]
check row["name"].getStr() == &"user{ids[i]}"
check row["email"].getStr() == &"user{ids[i]}@example.com"
check isNullish(row, "address")
check row["submit_on"].getStr() == &"2020-0{ids[i]}-01T00:00:00Z"
test("where int"):
var t = rdb.table("user").where("index", "=", 1).orderBy("index", Asc).first().waitFor().get()
check t["index"].getInt() == 1
check t["name"].getStr() == "user1"
check t["email"].getStr() == "user1@example.com"
check isNullish(t, "address")
check t["submit_on"].getStr() == "2020-01-01T00:00:00Z"
test("orWhere"):
let admin = rdb.table("auth").where("name", "=", "admin").first().waitFor().get()
let adminId = SurrealId.new(admin["id"].getStr())
var t = rdb.table("user").where("auth", "=", adminId).orWhere("name", "=", "user2").orderBy("index", Asc).get().waitFor()
const ids = [1,2,3,5,7,9]
for i, row in t:
check row["index"].getInt() == ids[i]
check row["name"].getStr() == &"user{ids[i]}"
check row["email"].getStr() == &"user{ids[i]}@example.com"
check isNullish(row, "address")
check row["submit_on"].getStr() == &"2020-0{ids[i]}-01T00:00:00Z"
test("whereBetween"):
var t = rdb
.table("user")
.whereBetween("index", [6, 9])
.orderBy("index", Asc)
.get()
.waitFor()
const ids = [6,7,8,9]
for i, row in t:
check row["index"].getInt() == ids[i]
check row["name"].getStr() == &"user{ids[i]}"
check row["email"].getStr() == &"user{ids[i]}@example.com"
check isNullish(row, "address")
check row["submit_on"].getStr() == &"2020-0{ids[i]}-01T00:00:00Z"
test("whereNotBetween"):
var t = rdb
.table("user")
.whereNotBetween("index", [2, 6])
.orderBy("index", Asc)
.get()
.waitFor()
const ids = [1,7,8,9]
for i, row in t:
check row["index"].getInt() == ids[i]
check row["name"].getStr() == &"user{ids[i]}"
check row["email"].getStr() == &"user{ids[i]}@example.com"
check isNullish(row, "address")
check row["submit_on"].getStr() == &"2020-0{ids[i]}-01T00:00:00Z"
test("whereIn"):
var t = rdb
.table("user")
.whereIn("index", @[5, 6, 7])
.orderBy("index", Asc)
.get()
.waitFor()
const ids = [5,6,7]
for i, row in t:
check row["index"].getInt() == ids[i]
check row["name"].getStr() == &"user{ids[i]}"
check row["email"].getStr() == &"user{ids[i]}@example.com"
check isNullish(row, "address")
check row["submit_on"].getStr() == &"2020-0{ids[i]}-01T00:00:00Z"
test("whereNotIn"):
var t = rdb
.table("user")
.whereNotIn("index", @[4, 5, 6, 7, 10])
.orderBy("index", Asc)
.get()
.waitFor()
const ids = [1,2,3,8,9]
for i, row in t:
check row["index"].getInt() == ids[i]
check row["name"].getStr() == &"user{ids[i]}"
check row["email"].getStr() == &"user{ids[i]}@example.com"
check isNullish(row, "address")
check row["submit_on"].getStr() == &"2020-0{ids[i]}-01T00:00:00Z"
test("whereNull"):
rdb.table("user").insert(%*{"email": "user11@example.com"}).waitFor()
var t = rdb
.table("user")
.whereNull("name")
.first()
.waitFor()
check t.get()["index"].getInt() == 11
setUp(rdb)
# TODO: not work
# test("groupBy"):
# var t = rdb
# .select("auth.name")
# .table("user")
# .groupBy("auth.name")
# .get()
# .waitFor()
# echo t
test("orderBy"):
var res = rdb.table("user").orderBy("name", Asc).get().waitFor()
check res[0]["name"].getStr() == "user1"
res = rdb.table("user").orderBy("name", Desc).get().waitFor()
check res[0]["name"].getStr() == "user9"
res = rdb.table("user").orderBy("index", Numeric, Asc).get().waitFor()
check res[0]["name"].getStr() == "user1"
res = rdb.table("user").orderBy("index", Numeric, Desc).get().waitFor()
check res[0]["name"].getStr() == "user10"
res = rdb.table("user").orderBy("name", Collate, Asc).get().waitFor()
check res[0]["name"].getStr() == "user1"
res = rdb.table("user").orderBy("name", Collate, Desc).get().waitFor()
check res[0]["name"].getStr() == "user9"
test("fetch"):
var res = rdb.table("user").where("name", "=", "user1").fetch("auth").first().waitFor().get()
check res["auth"]["name"].getStr() == "admin"
res = rdb.table("user").where("name", "=", "user2").fetch("auth").first().waitFor().get()
check res["auth"]["name"].getStr() == "user"
test("resultIsNull"):
check rdb.table("user").where("id", "=", 50).first().waitFor().isSome() == false
check newSeq[JsonNode](0) == rdb.table("user").where("id", "=", 50).get().waitFor()
suite($rdb & " insert"):
setup:
setup(rdb)
test("insert row"):
rdb.table("user").insert(%*{"name": "Alice"}).waitFor
let t = rdb.table("user").orderBy("index", Desc).first().waitFor().get()
check t["name"].getStr() == "Alice"
test("insert rows"):
rdb.table("user").insert(@[
%*{"name": "Alice"},
%*{"name": "Bob"},
]).waitFor
let t = rdb.table("user").orderBy("index", Desc).limit(2).get().waitFor()
check t[0]["name"].getStr() == "Bob"
check t[1]["name"].getStr() == "Alice"
test("insertId row"):
var id = rdb.table("user").insertId(%*{"name": "Alice"}).waitFor
echo id
let t = rdb.table("user").find(id).waitFor().get()
check t["name"].getStr() == "Alice"
test("insertId rows"):
let ids = rdb.table("user").insertId(@[
%*{"name": "Alice"},
%*{"name": "Bob"},
]).waitFor
var t = rdb.table("user").find(ids[0]).waitFor().get()
check t["name"].getStr == "Alice"
t = rdb.table("user").find(ids[1]).waitFor().get()
check t["name"].getStr == "Bob"
test("insertNil"):
var id = rdb.table("user").insertId(%*{
"name": "Alice",
"email": nil,
"address": ""
})
.waitFor
var res = rdb.table("user").find(id).waitFor
check not res.get.hasKey("email") or res.get["email"].kind == JNull
res = rdb.table("user").whereNull("email").first().waitFor
check not res.get.hasKey("email") or res.get["email"].kind == JNull
test("insert [T]"):
type User = object
name:string
let user = User(name: "Alice")
rdb.table("user").insert(user).waitFor
let row = rdb.table("user").orderBy("index", Desc).first().waitFor().get()
check row["name"].getStr() == "Alice"
test("insert seq[T]"):
type User = object
name:string
let users = @[
User(name: "Alice"),
User(name: "Bob"),
]
rdb.table("user").insert(users).waitFor
let t = rdb.table("user").orderBy("index", Desc).limit(2).get().waitFor()
check t[0]["name"].getStr() == "Bob"
check t[1]["name"].getStr() == "Alice"
suite($rdb & " update"):
setup:
setup(rdb)
test("update"):
var user1 = rdb.table("user").where("index", "=", 1).first().waitFor().get()
let user1Id = SurrealId.new(user1["id"].getStr())
rdb.table("user").where("id", "=", user1Id).update(%*{"name": "Alice"}).waitFor()
user1 = rdb.table("user").find(user1Id).waitFor().get()
check user1["name"].getStr() == "Alice"
test("update [T]"):
type User = object
name:string
let user = User(name: "Alice")
var user1 = rdb.table("user").where("index", "=", 1).first().waitFor().get()
let user1Id = SurrealId.new(user1["id"].getStr())
rdb.table("user").where("id", "=", user1Id).update(user).waitFor()
user1 = rdb.table("user").find(user1Id).waitFor().get()
check user1["name"].getStr() == "Alice"
test("update merge"):
var user1 = rdb.table("user").where("index", "=", 1).first().waitFor().get()
let user1Id = SurrealId.new(user1["id"].getStr())
rdb.update(user1Id, %*{"name": "updated"}).waitFor()
user1 = rdb.table("user").find(user1Id).waitFor().get()
check user1["name"].getStr() == "updated"
test("update merge [T]"):
type User = object
name:string
var user1 = rdb.table("user").where("index", "=", 1).first().waitFor().get()
let user1Id = SurrealId.new(user1["id"].getStr())
let user = User(name: "updated")
rdb.update(user1Id, user).waitFor()
user1 = rdb.table("user").find(user1Id).waitFor().get()
check user1["name"].getStr() == "updated"
suite($rdb & " delete"):
setup:
setup(rdb)
test("delete"):
let user1 = rdb.table("user").where("index", "=", 1).first().waitFor().get()
let user1Id = SurrealId.new(user1["id"].getStr())
rdb.table("user").where("name", "=", "user1").delete().waitFor()
check rdb.table("user").find(user1Id).waitFor().isSome() == false
test("delete id"):
let user1 = rdb.table("user").where("index", "=", 1).first().waitFor().get()
let user1Id = SurrealId.new(user1["id"].getStr())
rdb.table("user").delete(user1Id).waitFor
check rdb.table("user").find(user1Id).waitFor().isSome() == false
suite($rdb & " rawQuery"):
setup(rdb)
test("get"):
let sql = &"SELECT * FROM `user` WHERE `index` = ?"
let res = rdb.raw(sql, %*[1]).get().waitFor
check res[0]["name"].getStr == "user1"
test("first"):
let sql = &"SELECT * FROM `user` WHERE `index` = ?"
let res = rdb.raw(sql, %*[1]).first().waitFor().get()
check res["name"].getStr() == "user1"
test("exec"):
var sql = "SELECT * FROM `user` WHERE `index` = ?"
let user1 = rdb.raw(sql, %*[1]).first().waitFor().get()
let user1Id = SurrealId.new(user1["id"].getStr())
sql = "UPDATE `user` SET `name` = ? WHERE `id` = ?"
rdb.raw(sql, %*["updated", user1Id.rawId()]).exec().waitFor
sql = &"SELECT * FROM `user` WHERE `id` = ?"
let res = rdb.raw(sql, %[user1Id.rawId()]).get().waitFor
check res[0]["name"].getStr == "updated"
setup(rdb)
suite($rdb & " aggregates"):
test("count"):
var t = rdb.table("user").count().waitFor()
check t == 10
test("max"):
var t = rdb.table("user").max("name", Collate).waitFor()
check t == "user9"
var t2 = rdb.table("user").max("index", Numeric).waitFor()
check t2 == "10"
test("min"):
var t = rdb.table("user").min("name", Collate).waitFor()
check t == "user1"
var t2 = rdb.table("user").min("index", Numeric).waitFor()
check t2 == "1"
test("avg"):
let t = rdb.table("user").avg("index").waitFor()
check t == 5.5
test("sum"):
let t = rdb.table("user").sum("index").waitFor()
check t == 55.0
clearTables(rdb).waitFor()
@@ -0,0 +1,532 @@
discard """
cmd: "nim c -d:reset $file"
"""
# nim c -r -d:reset -d:ssl tests/surrealdb/test_schema.nim
import std/unittest
import std/asyncdispatch
import std/json
import std/options
import std/strutils
import ../../src/allographer/schema_builder
import ../../src/allographer/query_builder
import ./connections
import ../clear_tables
proc normalizeInfoDef(s: string): string =
result = s
result = result.replace("`rand`::uuid()", "rand::uuid()")
result = result.replace("1.1f", "1.1")
result = result.replace("| none", "| NONE")
result = result.replace("| None", "| NONE")
result = result.replace(" PERMISSIONS FULL", "")
result = result.replace("{ key: 'value' }", "{\"key\":\"value\"}")
result = result.replace("{ key: \"value\" }", "{\"key\":\"value\"}")
result = result.replace("{key:'value'}", "{\"key\":\"value\"}")
proc `==`(a, b: string): bool =
cmp(normalizeInfoDef(a), normalizeInfoDef(b)) == 0
proc expectInfoEntries(node: JsonNode, entries: seq[tuple[key: string, expected: string]]) =
for entry in entries:
check normalizeInfoDef(node[entry.key].getStr()) == normalizeInfoDef(entry.expected)
suite("SurrealDB create table"):
test("create table"):
surreal.create(
table("relation", [
Column.uuid("uuid").unique(),
]),
table("TypeIndex", [
Column.increments("index"),
Column.integer("integer").unsigned().index().default(1),
Column.smallInteger("smallInteger").unsigned().index().default(1),
Column.mediumInteger("mediumInteger").unsigned().index().default(1),
Column.bigInteger("bigInteger").unsigned().index().default(1),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1),
Column.double("double", 10, 3).unsigned().index().default(1.1),
Column.float("float").unsigned().index().default(1.1),
Column.uuid("uuid").index().default("A"),
Column.char("char", 256).index().default("A"),
Column.string("string").index().default("A"),
Column.text("text").index().default("A"),
Column.mediumText("mediumText").index().default("A"),
Column.longText("longText").index().default("A"),
Column.date("date").index().default(),
Column.datetime("datetime1").index().default(Current),
Column.datetime("datetime2").index().default(CurrentOnUpdate),
Column.timestamp("timestamp").index().default(),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary").index().default("A"),
Column.boolean("boolean").index().default(true),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A"),
Column.json("json").index().default(%*{"key":"value"}),
Column.foreign("relation").reference("id").onTable("relation").onDelete(SET_NULL).nullable(),
]),
table("TypeUnique", [
Column.increments("index"),
Column.integer("integer").unsigned().index().unique().unique().default(1),
Column.smallInteger("smallInteger").unsigned().index().unique().unique().default(1),
Column.mediumInteger("mediumInteger").unsigned().index().unique().unique().default(1),
Column.bigInteger("bigInteger").unsigned().index().unique().unique().default(1),
Column.decimal("decimal", 10, 3).unsigned().index().unique().unique().default(1.1),
Column.double("double", 10, 3).unsigned().index().unique().unique().default(1.1),
Column.float("float").unsigned().index().unique().unique().default(1.1),
Column.uuid("uuid").index().unique().unique().default("A"),
Column.char("char", 256).index().unique().unique().default("A"),
Column.string("string").index().unique().unique().default("A"),
Column.text("text").index().unique().unique().default("A"),
Column.mediumText("mediumText").index().unique().unique().default("A"),
Column.longText("longText").index().unique().unique().default("A"),
Column.date("date").index().unique().unique().default(),
Column.datetime("datetime1").index().unique().unique().default(Current),
Column.datetime("datetime2").index().unique().unique().default(CurrentOnUpdate),
Column.timestamp("timestamp").index().unique().unique().default(),
Column.timestamps(),
Column.softDelete(),
Column.binary("binary").index().unique().unique().default("A"),
Column.boolean("boolean").index().unique().unique().default(true),
Column.enumField("enumField", ["A", "B", "C"]).index().unique().default("A"),
Column.json("json").index().unique().default(%*{"key":"value"}),
Column.foreign("relation").reference("id").onTable("relation").onDelete(SET_NULL).nullable(),
]),
)
block:
let info = surreal.raw(""" INFO FOR TABLE TypeIndex """).info().waitFor()
let resultNode = info[0]["result"]
let fields = resultNode["fields"]
expectInfoEntries(fields, @[
("index", "DEFINE FIELD index ON TypeIndex TYPE int VALUE $value OR (SELECT max_index FROM _autoincrement_sequences WHERE `table` = 'TypeIndex' AND column = 'index' LIMIT 1)[0].max_index + 1"),
("integer", "DEFINE FIELD integer ON TypeIndex TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("smallInteger", "DEFINE FIELD smallInteger ON TypeIndex TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("mediumInteger", "DEFINE FIELD mediumInteger ON TypeIndex TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("bigInteger", "DEFINE FIELD bigInteger ON TypeIndex TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("decimal", "DEFINE FIELD decimal ON TypeIndex TYPE decimal VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("double", "DEFINE FIELD double ON TypeIndex TYPE decimal VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("float", "DEFINE FIELD float ON TypeIndex TYPE float VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("uuid", "DEFINE FIELD uuid ON TypeIndex TYPE string VALUE $value OR rand::uuid() ASSERT $value != NONE"),
("char", "DEFINE FIELD char ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT string::len($value) < 256 AND $value != NONE"),
("string", "DEFINE FIELD string ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT string::len($value) < 256 AND $value != NONE"),
("text", "DEFINE FIELD text ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("mediumText", "DEFINE FIELD mediumText ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("longText", "DEFINE FIELD longText ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("date", "DEFINE FIELD date ON TypeIndex TYPE datetime VALUE $value OR <datetime> '1970-01-01T00:00:00Z' ASSERT $value != NONE"),
("datetime1", "DEFINE FIELD datetime1 ON TypeIndex TYPE datetime VALUE $value OR time::now() ASSERT $value != NONE"),
("datetime2", "DEFINE FIELD datetime2 ON TypeIndex TYPE datetime VALUE time::now() ASSERT $value != NONE"),
("timestamp", "DEFINE FIELD timestamp ON TypeIndex TYPE datetime VALUE $value OR <datetime> '1970-01-01T00:00:00Z' ASSERT $value != NONE"),
("created_at", "DEFINE FIELD created_at ON TypeIndex TYPE datetime VALUE $value OR time::now()"),
("updated_at", "DEFINE FIELD updated_at ON TypeIndex TYPE datetime VALUE time::now()"),
("deleted_at", "DEFINE FIELD deleted_at ON TypeIndex TYPE datetime"),
("binary", "DEFINE FIELD binary ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("boolean", "DEFINE FIELD boolean ON TypeIndex TYPE bool VALUE $value OR true ASSERT $value != NONE"),
("enumField", "DEFINE FIELD enumField ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT $value INSIDE ['A', 'B', 'C'] AND $value != NONE"),
("json", "DEFINE FIELD json ON TypeIndex TYPE object FLEXIBLE VALUE $value OR { key: 'value' } ASSERT $value != NONE"),
("relation", "DEFINE FIELD relation ON TypeIndex TYPE record<relation> | NONE VALUE $value OR NONE"),
])
let indexs = if resultNode.hasKey("indexes"): resultNode["indexes"] else: resultNode["ix"]
expectInfoEntries(indexs, @[
("TypeIndex_integer_index", "DEFINE INDEX TypeIndex_integer_index ON TypeIndex FIELDS integer"),
("TypeIndex_smallInteger_index", "DEFINE INDEX TypeIndex_smallInteger_index ON TypeIndex FIELDS smallInteger"),
("TypeIndex_mediumInteger_index", "DEFINE INDEX TypeIndex_mediumInteger_index ON TypeIndex FIELDS mediumInteger"),
("TypeIndex_bigInteger_index", "DEFINE INDEX TypeIndex_bigInteger_index ON TypeIndex FIELDS bigInteger"),
("TypeIndex_decimal_index", "DEFINE INDEX TypeIndex_decimal_index ON TypeIndex FIELDS decimal"),
("TypeIndex_double_index", "DEFINE INDEX TypeIndex_double_index ON TypeIndex FIELDS double"),
("TypeIndex_float_index", "DEFINE INDEX TypeIndex_float_index ON TypeIndex FIELDS float"),
("TypeIndex_uuid_unique", "DEFINE INDEX TypeIndex_uuid_unique ON TypeIndex FIELDS uuid UNIQUE"),
("TypeIndex_char_index", "DEFINE INDEX TypeIndex_char_index ON TypeIndex FIELDS char"),
("TypeIndex_string_index", "DEFINE INDEX TypeIndex_string_index ON TypeIndex FIELDS string"),
("TypeIndex_text_index", "DEFINE INDEX TypeIndex_text_index ON TypeIndex FIELDS text"),
("TypeIndex_mediumText_index", "DEFINE INDEX TypeIndex_mediumText_index ON TypeIndex FIELDS mediumText"),
("TypeIndex_longText_index", "DEFINE INDEX TypeIndex_longText_index ON TypeIndex FIELDS longText"),
("TypeIndex_date_index", "DEFINE INDEX TypeIndex_date_index ON TypeIndex FIELDS date"),
("TypeIndex_datetime1_index", "DEFINE INDEX TypeIndex_datetime1_index ON TypeIndex FIELDS datetime1"),
("TypeIndex_datetime2_index", "DEFINE INDEX TypeIndex_datetime2_index ON TypeIndex FIELDS datetime2"),
("TypeIndex_timestamp_index", "DEFINE INDEX TypeIndex_timestamp_index ON TypeIndex FIELDS timestamp"),
("TypeIndex_created_at_index", "DEFINE INDEX TypeIndex_created_at_index ON TypeIndex FIELDS created_at"),
("TypeIndex_updated_at_index", "DEFINE INDEX TypeIndex_updated_at_index ON TypeIndex FIELDS updated_at"),
("TypeIndex_deleted_at_index", "DEFINE INDEX TypeIndex_deleted_at_index ON TypeIndex FIELDS deleted_at"),
("TypeIndex_binary_index", "DEFINE INDEX TypeIndex_binary_index ON TypeIndex FIELDS binary"),
("TypeIndex_boolean_index", "DEFINE INDEX TypeIndex_boolean_index ON TypeIndex FIELDS boolean"),
("TypeIndex_enumField_index", "DEFINE INDEX TypeIndex_enumField_index ON TypeIndex FIELDS enumField"),
("TypeIndex_json_index", "DEFINE INDEX TypeIndex_json_index ON TypeIndex FIELDS json"),
])
block:
let info = surreal.raw(""" INFO FOR TABLE TypeUnique """).info().waitFor()
let resultNode = info[0]["result"]
let fields = resultNode["fields"]
expectInfoEntries(fields, @[
("index", "DEFINE FIELD index ON TypeUnique TYPE int VALUE $value OR (SELECT max_index FROM _autoincrement_sequences WHERE `table` = 'TypeUnique' AND column = 'index' LIMIT 1)[0].max_index + 1"),
("integer", "DEFINE FIELD integer ON TypeUnique TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("smallInteger", "DEFINE FIELD smallInteger ON TypeUnique TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("mediumInteger", "DEFINE FIELD mediumInteger ON TypeUnique TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("bigInteger", "DEFINE FIELD bigInteger ON TypeUnique TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("decimal", "DEFINE FIELD decimal ON TypeUnique TYPE decimal VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("double", "DEFINE FIELD double ON TypeUnique TYPE decimal VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("float", "DEFINE FIELD float ON TypeUnique TYPE float VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("uuid", "DEFINE FIELD uuid ON TypeUnique TYPE string VALUE $value OR rand::uuid() ASSERT $value != NONE"),
("char", "DEFINE FIELD char ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT string::len($value) < 256 AND $value != NONE"),
("string", "DEFINE FIELD string ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT string::len($value) < 256 AND $value != NONE"),
("text", "DEFINE FIELD text ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("mediumText", "DEFINE FIELD mediumText ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("longText", "DEFINE FIELD longText ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("date", "DEFINE FIELD date ON TypeUnique TYPE datetime VALUE $value OR <datetime> '1970-01-01T00:00:00Z' ASSERT $value != NONE"),
("datetime1", "DEFINE FIELD datetime1 ON TypeUnique TYPE datetime VALUE $value OR time::now() ASSERT $value != NONE"),
("datetime2", "DEFINE FIELD datetime2 ON TypeUnique TYPE datetime VALUE time::now() ASSERT $value != NONE"),
("timestamp", "DEFINE FIELD timestamp ON TypeUnique TYPE datetime VALUE $value OR <datetime> '1970-01-01T00:00:00Z' ASSERT $value != NONE"),
("created_at", "DEFINE FIELD created_at ON TypeUnique TYPE datetime VALUE $value OR time::now()"),
("updated_at", "DEFINE FIELD updated_at ON TypeUnique TYPE datetime VALUE time::now()"),
("deleted_at", "DEFINE FIELD deleted_at ON TypeUnique TYPE datetime"),
("binary", "DEFINE FIELD binary ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("boolean", "DEFINE FIELD boolean ON TypeUnique TYPE bool VALUE $value OR true ASSERT $value != NONE"),
("enumField", "DEFINE FIELD enumField ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT $value INSIDE ['A', 'B', 'C'] AND $value != NONE"),
("json", "DEFINE FIELD json ON TypeUnique TYPE object FLEXIBLE VALUE $value OR { key: 'value' } ASSERT $value != NONE"),
("relation", "DEFINE FIELD relation ON TypeUnique TYPE record<relation> | NONE VALUE $value OR NONE"),
])
let indexs = resultNode["indexes"]
expectInfoEntries(indexs, @[
("TypeUnique_integer_unique", "DEFINE INDEX TypeUnique_integer_unique ON TypeUnique FIELDS integer UNIQUE"),
("TypeUnique_smallInteger_unique", "DEFINE INDEX TypeUnique_smallInteger_unique ON TypeUnique FIELDS smallInteger UNIQUE"),
("TypeUnique_mediumInteger_unique", "DEFINE INDEX TypeUnique_mediumInteger_unique ON TypeUnique FIELDS mediumInteger UNIQUE"),
("TypeUnique_bigInteger_unique", "DEFINE INDEX TypeUnique_bigInteger_unique ON TypeUnique FIELDS bigInteger UNIQUE"),
("TypeUnique_decimal_unique", "DEFINE INDEX TypeUnique_decimal_unique ON TypeUnique FIELDS decimal UNIQUE"),
("TypeUnique_double_unique", "DEFINE INDEX TypeUnique_double_unique ON TypeUnique FIELDS double UNIQUE"),
("TypeUnique_float_unique", "DEFINE INDEX TypeUnique_float_unique ON TypeUnique FIELDS float UNIQUE"),
("TypeUnique_uuid_unique", "DEFINE INDEX TypeUnique_uuid_unique ON TypeUnique FIELDS uuid UNIQUE"),
("TypeUnique_char_unique", "DEFINE INDEX TypeUnique_char_unique ON TypeUnique FIELDS char UNIQUE"),
("TypeUnique_string_unique", "DEFINE INDEX TypeUnique_string_unique ON TypeUnique FIELDS string UNIQUE"),
("TypeUnique_text_unique", "DEFINE INDEX TypeUnique_text_unique ON TypeUnique FIELDS text UNIQUE"),
("TypeUnique_mediumText_unique", "DEFINE INDEX TypeUnique_mediumText_unique ON TypeUnique FIELDS mediumText UNIQUE"),
("TypeUnique_longText_unique", "DEFINE INDEX TypeUnique_longText_unique ON TypeUnique FIELDS longText UNIQUE"),
("TypeUnique_date_unique", "DEFINE INDEX TypeUnique_date_unique ON TypeUnique FIELDS date UNIQUE"),
("TypeUnique_datetime1_unique", "DEFINE INDEX TypeUnique_datetime1_unique ON TypeUnique FIELDS datetime1 UNIQUE"),
("TypeUnique_datetime2_unique", "DEFINE INDEX TypeUnique_datetime2_unique ON TypeUnique FIELDS datetime2 UNIQUE"),
("TypeUnique_timestamp_unique", "DEFINE INDEX TypeUnique_timestamp_unique ON TypeUnique FIELDS timestamp UNIQUE"),
("TypeUnique_created_at_index", "DEFINE INDEX TypeUnique_created_at_index ON TypeUnique FIELDS created_at"),
("TypeUnique_updated_at_index", "DEFINE INDEX TypeUnique_updated_at_index ON TypeUnique FIELDS updated_at"),
("TypeUnique_deleted_at_index", "DEFINE INDEX TypeUnique_deleted_at_index ON TypeUnique FIELDS deleted_at"),
("TypeUnique_binary_unique", "DEFINE INDEX TypeUnique_binary_unique ON TypeUnique FIELDS binary UNIQUE"),
("TypeUnique_boolean_unique", "DEFINE INDEX TypeUnique_boolean_unique ON TypeUnique FIELDS boolean UNIQUE"),
("TypeUnique_enumField_unique", "DEFINE INDEX TypeUnique_enumField_unique ON TypeUnique FIELDS enumField UNIQUE"),
("TypeUnique_json_unique", "DEFINE INDEX TypeUnique_json_unique ON TypeUnique FIELDS json UNIQUE"),
])
test("autoincrement"):
surreal.create(
table("test",[
Column.increments("index"),
Column.integer("index2").autoIncrement(),
Column.string("string")
])
)
surreal.table("test").insert(%*{"string": "a"}).waitFor
surreal.table("test").insert(%*{"string": "b"}).waitFor
surreal.table("test").insert(%*{"string": "c"}).waitFor
surreal.table("test").where("string", "=", "b").delete().waitFor
surreal.table("test").insert(%*{"string": "d"}).waitFor
let data = surreal.table("test").orderBy("index", Asc).get().waitFor
for row in data:
if row["string"].getStr == "a":
check row["index"].getInt == 1
check row["index2"].getInt == 1
if row["string"].getStr == "c":
check row["index"].getInt == 3
check row["index2"].getInt == 3
if row["string"].getStr == "d":
check row["index2"].getInt == 4
suite("Datetime"):
test("datetime default"):
surreal.create(
table("test", [
Column.string("name"),
Column.datetime("created_at").default(Current),
Column.datetime("updated_at").default(CurrentOnUpdate),
])
)
surreal.table("test").insert(%*{"name": "alice"}).waitFor()
var alice = surreal.table("test").first().waitFor().get()
let aliceId = SurrealId.new(alice["id"].getStr())
echo alice
let aliceCreatedAt1 = alice["created_at"].getStr()
let aliceUpdatedAt1 = alice["updated_at"].getStr()
surreal.table("test").where("id", "=", aliceId).update(%*{"name": "updated"}).waitFor()
alice = surreal.table("test").find(aliceId).waitFor().get()
echo alice
let aliceCreatedAt2 = alice["created_at"].getStr()
let aliceUpdatedAt2 = alice["updated_at"].getStr()
check aliceCreatedAt1 == aliceCreatedAt2
check aliceUpdatedAt1 != aliceUpdatedAt2
test("timestamps"):
surreal.create(
table("test", [
Column.string("name"),
Column.timestamps()
])
)
surreal.table("test").insert(%*{"name": "alice"}).waitFor()
var alice = surreal.table("test").first().waitFor().get()
let aliceId = SurrealId.new(alice["id"].getStr())
let aliceCreatedAt1 = alice["created_at"].getStr()
let aliceUpdatedAt1 = alice["updated_at"].getStr()
surreal.table("test").where("id", "=", aliceId).update(%*{"name": "updated"}).waitFor()
alice = surreal.table("test").find(aliceId).waitFor().get()
let aliceCreatedAt2 = alice["created_at"].getStr()
let aliceUpdatedAt2 = alice["updated_at"].getStr()
check aliceCreatedAt1 == aliceCreatedAt2
check aliceUpdatedAt1 != aliceUpdatedAt2
suite("SurrealDB alter table"):
setup:
surreal.create(
table("relation", [
Column.uuid("uuid").unique(),
]),
table("TypeUnique", [
Column.integer("num"),
Column.string("str"),
]),
table("TypeIndex", [
Column.integer("num"),
Column.string("str"),
])
)
test("add column"):
surreal.alter(
table("TypeIndex", [
Column.increments("index").add(),
Column.integer("integer").unsigned().index().default(1).add(),
Column.smallInteger("smallInteger").unsigned().index().default(1).add(),
Column.mediumInteger("mediumInteger").unsigned().index().default(1).add(),
Column.bigInteger("bigInteger").unsigned().index().default(1).add(),
Column.decimal("decimal", 10, 3).unsigned().index().default(1.1).add(),
Column.double("double", 10, 3).unsigned().index().default(1.1).add(),
Column.float("float").unsigned().index().default(1.1).add(),
Column.uuid("uuid").index().default("A").add(),
Column.char("char", 256).index().default("A").add(),
Column.string("string").index().default("A").add(),
Column.text("text").index().default("A").add(),
Column.mediumText("mediumText").index().default("A").add(),
Column.longText("longText").index().default("A").add(),
Column.date("date").index().default().add(),
Column.datetime("datetime1").index().default(Current).add(),
Column.datetime("datetime2").index().default(CurrentOnUpdate).add(),
Column.timestamp("timestamp").index().default().add(),
Column.timestamps().add(),
Column.softDelete().add(),
Column.binary("binary").index().default("A").add(),
Column.boolean("boolean").index().default(true).add(),
Column.enumField("enumField", ["A", "B", "C"]).index().default("A").add(),
Column.json("json").index().default(%*{"key":"value"}).add(),
Column.foreign("relation").reference("id").onTable("relation").onDelete(SET_NULL).nullable().add(),
]),
table("TypeUnique", [
Column.increments("index").add(),
Column.integer("integer").unsigned().index().unique().unique().default(1).add(),
Column.smallInteger("smallInteger").unsigned().index().unique().unique().default(1).add(),
Column.mediumInteger("mediumInteger").unsigned().index().unique().unique().default(1).add(),
Column.bigInteger("bigInteger").unsigned().index().unique().unique().default(1).add(),
Column.decimal("decimal", 10, 3).unsigned().index().unique().unique().default(1.1).add(),
Column.double("double", 10, 3).unsigned().index().unique().unique().default(1.1).add(),
Column.float("float").unsigned().index().unique().unique().default(1.1).add(),
Column.uuid("uuid").index().unique().unique().default("A").add(),
Column.char("char", 256).index().unique().unique().default("A").add(),
Column.string("string").index().unique().unique().default("A").add(),
Column.text("text").index().unique().unique().default("A").add(),
Column.mediumText("mediumText").index().unique().unique().default("A").add(),
Column.longText("longText").index().unique().unique().default("A").add(),
Column.date("date").index().unique().unique().default().add(),
Column.datetime("datetime1").index().unique().unique().default(Current).add(),
Column.datetime("datetime2").index().unique().unique().default(CurrentOnUpdate).add(),
Column.timestamp("timestamp").index().unique().unique().default().add(),
Column.timestamps().add(),
Column.softDelete().add(),
Column.binary("binary").index().unique().unique().default("A").add(),
Column.boolean("boolean").index().unique().unique().default(true).add(),
Column.enumField("enumField", ["A", "B", "C"]).index().unique().default("A").add(),
Column.json("json").index().unique().default(%*{"key":"value"}).add(),
Column.foreign("relation").reference("id").onTable("relation").onDelete(SET_NULL).nullable().add(),
]),
)
block:
let info = surreal.raw(""" INFO FOR TABLE TypeIndex """).info().waitFor()
let resultNode = info[0]["result"]
let fields = resultNode["fields"]
expectInfoEntries(fields, @[
("index", "DEFINE FIELD index ON TypeIndex TYPE int VALUE $value OR (SELECT max_index FROM _autoincrement_sequences WHERE `table` = 'TypeIndex' AND column = 'index' LIMIT 1)[0].max_index + 1"),
("integer", "DEFINE FIELD integer ON TypeIndex TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("smallInteger", "DEFINE FIELD smallInteger ON TypeIndex TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("mediumInteger", "DEFINE FIELD mediumInteger ON TypeIndex TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("bigInteger", "DEFINE FIELD bigInteger ON TypeIndex TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("decimal", "DEFINE FIELD decimal ON TypeIndex TYPE decimal VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("double", "DEFINE FIELD double ON TypeIndex TYPE decimal VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("float", "DEFINE FIELD float ON TypeIndex TYPE float VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("uuid", "DEFINE FIELD uuid ON TypeIndex TYPE string VALUE $value OR rand::uuid() ASSERT $value != NONE"),
("char", "DEFINE FIELD char ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT string::len($value) < 256 AND $value != NONE"),
("string", "DEFINE FIELD string ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT string::len($value) < 256 AND $value != NONE"),
("text", "DEFINE FIELD text ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("mediumText", "DEFINE FIELD mediumText ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("longText", "DEFINE FIELD longText ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("date", "DEFINE FIELD date ON TypeIndex TYPE datetime VALUE $value OR <datetime> '1970-01-01T00:00:00Z' ASSERT $value != NONE"),
("datetime1", "DEFINE FIELD datetime1 ON TypeIndex TYPE datetime VALUE $value OR time::now() ASSERT $value != NONE"),
("datetime2", "DEFINE FIELD datetime2 ON TypeIndex TYPE datetime VALUE time::now() ASSERT $value != NONE"),
("timestamp", "DEFINE FIELD timestamp ON TypeIndex TYPE datetime VALUE $value OR <datetime> '1970-01-01T00:00:00Z' ASSERT $value != NONE"),
("created_at", "DEFINE FIELD created_at ON TypeIndex TYPE datetime VALUE $value OR time::now()"),
("updated_at", "DEFINE FIELD updated_at ON TypeIndex TYPE datetime VALUE time::now()"),
("deleted_at", "DEFINE FIELD deleted_at ON TypeIndex TYPE datetime"),
("binary", "DEFINE FIELD binary ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("boolean", "DEFINE FIELD boolean ON TypeIndex TYPE bool VALUE $value OR true ASSERT $value != NONE"),
("enumField", "DEFINE FIELD enumField ON TypeIndex TYPE string VALUE $value OR 'A' ASSERT $value INSIDE ['A', 'B', 'C'] AND $value != NONE"),
("json", "DEFINE FIELD json ON TypeIndex TYPE object FLEXIBLE VALUE $value OR { key: 'value' } ASSERT $value != NONE"),
("relation", "DEFINE FIELD relation ON TypeIndex TYPE record<relation> | NONE VALUE $value OR NONE"),
])
let indexs = resultNode["indexes"]
expectInfoEntries(indexs, @[
("TypeIndex_integer_index", "DEFINE INDEX TypeIndex_integer_index ON TypeIndex FIELDS integer"),
("TypeIndex_smallInteger_index", "DEFINE INDEX TypeIndex_smallInteger_index ON TypeIndex FIELDS smallInteger"),
("TypeIndex_mediumInteger_index", "DEFINE INDEX TypeIndex_mediumInteger_index ON TypeIndex FIELDS mediumInteger"),
("TypeIndex_bigInteger_index", "DEFINE INDEX TypeIndex_bigInteger_index ON TypeIndex FIELDS bigInteger"),
("TypeIndex_decimal_index", "DEFINE INDEX TypeIndex_decimal_index ON TypeIndex FIELDS decimal"),
("TypeIndex_double_index", "DEFINE INDEX TypeIndex_double_index ON TypeIndex FIELDS double"),
("TypeIndex_float_index", "DEFINE INDEX TypeIndex_float_index ON TypeIndex FIELDS float"),
("TypeIndex_uuid_unique", "DEFINE INDEX TypeIndex_uuid_unique ON TypeIndex FIELDS uuid UNIQUE"),
("TypeIndex_char_index", "DEFINE INDEX TypeIndex_char_index ON TypeIndex FIELDS char"),
("TypeIndex_string_index", "DEFINE INDEX TypeIndex_string_index ON TypeIndex FIELDS string"),
("TypeIndex_text_index", "DEFINE INDEX TypeIndex_text_index ON TypeIndex FIELDS text"),
("TypeIndex_mediumText_index", "DEFINE INDEX TypeIndex_mediumText_index ON TypeIndex FIELDS mediumText"),
("TypeIndex_longText_index", "DEFINE INDEX TypeIndex_longText_index ON TypeIndex FIELDS longText"),
("TypeIndex_date_index", "DEFINE INDEX TypeIndex_date_index ON TypeIndex FIELDS date"),
("TypeIndex_datetime1_index", "DEFINE INDEX TypeIndex_datetime1_index ON TypeIndex FIELDS datetime1"),
("TypeIndex_datetime2_index", "DEFINE INDEX TypeIndex_datetime2_index ON TypeIndex FIELDS datetime2"),
("TypeIndex_timestamp_index", "DEFINE INDEX TypeIndex_timestamp_index ON TypeIndex FIELDS timestamp"),
("TypeIndex_created_at_index", "DEFINE INDEX TypeIndex_created_at_index ON TypeIndex FIELDS created_at"),
("TypeIndex_updated_at_index", "DEFINE INDEX TypeIndex_updated_at_index ON TypeIndex FIELDS updated_at"),
("TypeIndex_deleted_at_index", "DEFINE INDEX TypeIndex_deleted_at_index ON TypeIndex FIELDS deleted_at"),
("TypeIndex_binary_index", "DEFINE INDEX TypeIndex_binary_index ON TypeIndex FIELDS binary"),
("TypeIndex_boolean_index", "DEFINE INDEX TypeIndex_boolean_index ON TypeIndex FIELDS boolean"),
("TypeIndex_enumField_index", "DEFINE INDEX TypeIndex_enumField_index ON TypeIndex FIELDS enumField"),
("TypeIndex_json_index", "DEFINE INDEX TypeIndex_json_index ON TypeIndex FIELDS json"),
])
block:
let info = surreal.raw(""" INFO FOR TABLE TypeUnique """).info().waitFor()
let resultNode = info[0]["result"]
let fields = resultNode["fields"]
expectInfoEntries(fields, @[
("index", "DEFINE FIELD index ON TypeUnique TYPE int VALUE $value OR (SELECT max_index FROM _autoincrement_sequences WHERE `table` = 'TypeUnique' AND column = 'index' LIMIT 1)[0].max_index + 1"),
("integer", "DEFINE FIELD integer ON TypeUnique TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("smallInteger", "DEFINE FIELD smallInteger ON TypeUnique TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("mediumInteger", "DEFINE FIELD mediumInteger ON TypeUnique TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("bigInteger", "DEFINE FIELD bigInteger ON TypeUnique TYPE int VALUE $value OR 1 ASSERT $value != NONE AND $value >= 0"),
("decimal", "DEFINE FIELD decimal ON TypeUnique TYPE decimal VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("double", "DEFINE FIELD double ON TypeUnique TYPE decimal VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("float", "DEFINE FIELD float ON TypeUnique TYPE float VALUE $value OR 1.1 ASSERT $value != NONE AND $value >= 0"),
("uuid", "DEFINE FIELD uuid ON TypeUnique TYPE string VALUE $value OR rand::uuid() ASSERT $value != NONE"),
("char", "DEFINE FIELD char ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT string::len($value) < 256 AND $value != NONE"),
("string", "DEFINE FIELD string ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT string::len($value) < 256 AND $value != NONE"),
("text", "DEFINE FIELD text ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("mediumText", "DEFINE FIELD mediumText ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("longText", "DEFINE FIELD longText ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("date", "DEFINE FIELD date ON TypeUnique TYPE datetime VALUE $value OR <datetime> '1970-01-01T00:00:00Z' ASSERT $value != NONE"),
("datetime1", "DEFINE FIELD datetime1 ON TypeUnique TYPE datetime VALUE $value OR time::now() ASSERT $value != NONE"),
("datetime2", "DEFINE FIELD datetime2 ON TypeUnique TYPE datetime VALUE time::now() ASSERT $value != NONE"),
("timestamp", "DEFINE FIELD timestamp ON TypeUnique TYPE datetime VALUE $value OR <datetime> '1970-01-01T00:00:00Z' ASSERT $value != NONE"),
("created_at", "DEFINE FIELD created_at ON TypeUnique TYPE datetime VALUE $value OR time::now()"),
("updated_at", "DEFINE FIELD updated_at ON TypeUnique TYPE datetime VALUE time::now()"),
("deleted_at", "DEFINE FIELD deleted_at ON TypeUnique TYPE datetime"),
("binary", "DEFINE FIELD binary ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT $value != NONE"),
("boolean", "DEFINE FIELD boolean ON TypeUnique TYPE bool VALUE $value OR true ASSERT $value != NONE"),
("enumField", "DEFINE FIELD enumField ON TypeUnique TYPE string VALUE $value OR 'A' ASSERT $value INSIDE ['A', 'B', 'C'] AND $value != NONE"),
("json", "DEFINE FIELD json ON TypeUnique TYPE object FLEXIBLE VALUE $value OR { key: 'value' } ASSERT $value != NONE"),
("relation", "DEFINE FIELD relation ON TypeUnique TYPE record<relation> | NONE VALUE $value OR NONE"),
])
let indexs = if resultNode.hasKey("indexes"): resultNode["indexes"] else: resultNode["ix"]
expectInfoEntries(indexs, @[
("TypeUnique_integer_unique", "DEFINE INDEX TypeUnique_integer_unique ON TypeUnique FIELDS integer UNIQUE"),
("TypeUnique_smallInteger_unique", "DEFINE INDEX TypeUnique_smallInteger_unique ON TypeUnique FIELDS smallInteger UNIQUE"),
("TypeUnique_mediumInteger_unique", "DEFINE INDEX TypeUnique_mediumInteger_unique ON TypeUnique FIELDS mediumInteger UNIQUE"),
("TypeUnique_bigInteger_unique", "DEFINE INDEX TypeUnique_bigInteger_unique ON TypeUnique FIELDS bigInteger UNIQUE"),
("TypeUnique_decimal_unique", "DEFINE INDEX TypeUnique_decimal_unique ON TypeUnique FIELDS decimal UNIQUE"),
("TypeUnique_double_unique", "DEFINE INDEX TypeUnique_double_unique ON TypeUnique FIELDS double UNIQUE"),
("TypeUnique_float_unique", "DEFINE INDEX TypeUnique_float_unique ON TypeUnique FIELDS float UNIQUE"),
("TypeUnique_uuid_unique", "DEFINE INDEX TypeUnique_uuid_unique ON TypeUnique FIELDS uuid UNIQUE"),
("TypeUnique_char_unique", "DEFINE INDEX TypeUnique_char_unique ON TypeUnique FIELDS char UNIQUE"),
("TypeUnique_string_unique", "DEFINE INDEX TypeUnique_string_unique ON TypeUnique FIELDS string UNIQUE"),
("TypeUnique_text_unique", "DEFINE INDEX TypeUnique_text_unique ON TypeUnique FIELDS text UNIQUE"),
("TypeUnique_mediumText_unique", "DEFINE INDEX TypeUnique_mediumText_unique ON TypeUnique FIELDS mediumText UNIQUE"),
("TypeUnique_longText_unique", "DEFINE INDEX TypeUnique_longText_unique ON TypeUnique FIELDS longText UNIQUE"),
("TypeUnique_date_unique", "DEFINE INDEX TypeUnique_date_unique ON TypeUnique FIELDS date UNIQUE"),
("TypeUnique_datetime1_unique", "DEFINE INDEX TypeUnique_datetime1_unique ON TypeUnique FIELDS datetime1 UNIQUE"),
("TypeUnique_datetime2_unique", "DEFINE INDEX TypeUnique_datetime2_unique ON TypeUnique FIELDS datetime2 UNIQUE"),
("TypeUnique_timestamp_unique", "DEFINE INDEX TypeUnique_timestamp_unique ON TypeUnique FIELDS timestamp UNIQUE"),
("TypeUnique_created_at_index", "DEFINE INDEX TypeUnique_created_at_index ON TypeUnique FIELDS created_at"),
("TypeUnique_updated_at_index", "DEFINE INDEX TypeUnique_updated_at_index ON TypeUnique FIELDS updated_at"),
("TypeUnique_deleted_at_index", "DEFINE INDEX TypeUnique_deleted_at_index ON TypeUnique FIELDS deleted_at"),
("TypeUnique_binary_unique", "DEFINE INDEX TypeUnique_binary_unique ON TypeUnique FIELDS binary UNIQUE"),
("TypeUnique_boolean_unique", "DEFINE INDEX TypeUnique_boolean_unique ON TypeUnique FIELDS boolean UNIQUE"),
("TypeUnique_enumField_unique", "DEFINE INDEX TypeUnique_enumField_unique ON TypeUnique FIELDS enumField UNIQUE"),
("TypeUnique_json_unique", "DEFINE INDEX TypeUnique_json_unique ON TypeUnique FIELDS json UNIQUE"),
])
test("drop column"):
surreal.alter(
table("TypeIndex", [
Column.dropColumn("str")
])
)
let columns = surreal.table("TypeIndex").columns().waitFor
check not columns.contains("str")
test("drop table"):
surreal.create(
table("TypeIndex", [
Column.integer("num")
])
)
surreal.drop(
table("TypeIndex")
)
let res = surreal.table("TypeIndex").first().waitFor()
check not res.isSome
clearTables(surreal).waitFor()
@@ -0,0 +1,42 @@
discard """
cmd: "nim c -d:reset $file"
"""
import std/unittest
import ../../src/allographer/query_builder/libs/database_url
suite("database url"):
test("parse query and credentials"):
let url = parseDatabaseUrl(
"postgresql://user%40mail:pa%3Ass@host:5432/db%2Fname?sslmode=require&application_name=allographer"
)
check url.scheme == "postgresql"
check url.username == "user@mail"
check url.password == "pa:ss"
check url.hostname == "host"
check url.hasPort
check url.port == 5432
check url.databaseName == "db/name"
check url.query == @[
(key: "sslmode", value: "require"),
(key: "application_name", value: "allographer"),
]
test("postgres alias stays valid"):
let url = parseDatabaseUrl("postgres://user:pass@host:5432/database")
check url.scheme == "postgres"
test("port defaults are explicit"):
let url = parseDatabaseUrl("mysql://user:pass@host/database")
check not url.hasPort
check portOrDefault(url, 3306) == 3306
test("sqlite path from url"):
let url = parseDatabaseUrl("sqlite://./relative/path.db")
check sqliteDatabasePath(url) == "./relative/path.db"
check sqliteDatabasePath(parseDatabaseUrl("sqlite://relative/path.db")) == "relative/path.db"
@@ -0,0 +1,16 @@
discard """
cmd: "nim c -d:reset $file"
"""
# nim c -r tests/utils/test_utils.nim
import std/unittest
import ../../src/allographer/utils/snake_to_camel
suite("test utils"):
test("snakeToCamel"):
check snakeToCamel("test") == "Test"
check snakeToCamel("test_case") == "TestCase"
check snakeToCamel("test_case_abc") == "TestCaseAbc"
check snakeToCamel("test_case_1") == "TestCase1"