Files
Baradb/clients/nim/ormin/tests/tfeature.nim
T
dimgigov 4e54075078
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
Add Ormin ORM support for BaraDB (Nim client)
2026-05-15 21:49:08 +03:00

823 lines
23 KiB
Nim

import unittest, strformat, sequtils, algorithm, sugar, json, tables, random
import os
import ormin
import ormin/db_utils
when NimVersion < "1.2.0": import ./compat
let testDir = currentSourcePath.parentDir()
when defined postgre:
when defined(macosx):
{.passL: "-Wl,-rpath,/opt/homebrew/lib/postgresql@14".}
from db_connector/db_postgres import exec, getValue
const backend = DbBackend.postgre
importModel(backend, "forum_model_postgres")
const sqlFileName = "forum_model_postgres.sql"
let db {.global.} = open("localhost", "test", "test", "test_ormin")
else:
from db_connector/db_sqlite import exec, getValue
const backend = DbBackend.sqlite
importModel(backend, "forum_model_sqlite")
const sqlFileName = "forum_model_sqlite.sql"
var memoryPath = testDir & "/" & ":memory:"
let db {.global.} = open(memoryPath, "", "", "")
var sqlFilePath = Path(testDir & "/" & sqlFileName)
type
Person = tuple[id: int,
name: string,
password: string,
email: string,
salt: string,
status: string]
Thread = tuple[id: int,
name: string,
views: int]
Post = tuple[id: int,
author: int,
ip: string,
header: string,
content: string,
thread: int]
Antibot = tuple[id: int,
ip: string,
answer: string]
const
personcount = 5
threadcount = 5
postcount = 10
antibotcount = 5
var
persondata: seq[Person]
threaddata: seq[Thread]
postdata: seq[Post]
antibotdata: seq[Antibot]
suite &"Test ormin features of {backend}":
discard
suite "query":
db.dropTable(sqlFilePath)
db.createTable(sqlFilePath)
static:
doAssert compiles(block:
discard query:
select post(author)
crossjoin person(name)
)
doAssert compiles(block:
discard query:
select post(author)
outerjoin person(name) on author == id
)
doAssert compiles(block:
discard query:
select post(author)
leftjoin person(name) on author == id
)
doAssert compiles(block:
discard query:
select post(author)
leftouterjoin person(name) on author == id
)
doAssert compiles(block:
discard query:
select post(author)
rightjoin person(name) on author == id
)
doAssert compiles(block:
discard query:
select post(author)
rightouterjoin person(name) on author == id
)
doAssert compiles(block:
discard query:
select post(author)
fulljoin person(name) on author == id
)
doAssert compiles(block:
discard query:
select post(author)
fullouterjoin person(name) on author == id
)
# prepare data to insert
for i in 1..personcount:
persondata.add((id: i,
name: fmt"john{i}",
password: fmt"pass{i}",
email: fmt"john{i}@mail.com",
salt: fmt"abcd{i}",
status: fmt"ok{i}"))
for i in 1..threadcount:
threaddata.add((id: i,
name: fmt"thread{i}",
views: i))
for i in 1..postcount:
postdata.add((id: i,
author: sample({1..personcount}),
ip: "",
header: fmt"title{i}",
content: fmt"content{i}",
thread: sample({1..threadcount})))
for i in 1..antibotcount:
antibotdata.add((id: i,
ip: "",
answer: fmt"answer{i}"))
# insert data into database
let
insertperson = sql"insert into person (id, name, password, email, salt, status) values (?, ?, ?, ?, ?, ?)"
insertthread = sql"insert into thread (id, name, views) values (?, ?, ?)"
insertpost = sql"insert into post (id, author, ip, header, content, thread) values (?, ?, ?, ?, ?, ?)"
insertantibot = sql"insert into antibot (id, ip, answer) values (?, ?, ?)"
for p in persondata:
db.exec(insertperson, p.id, p.name, p.password, p.email, p.salt, p.status)
for t in threaddata:
db.exec(insertthread, t.id, t.name, t.views)
for p in postdata:
db.exec(insertpost, p.id, p.author, p.ip, p.header, p.content, p.thread)
for a in antibotdata:
db.exec(insertantibot, a.id, a.ip, a.answer)
# check data in database
let personexpected = db.getValue(sql"select count(*) from person")
assert personexpected == $personcount
let threadexpected = db.getValue(sql"select count(*) from thread")
assert threadexpected == $threadcount
let postexpected = db.getValue(sql"select count(*) from post")
assert postexpected == $postcount
let antibotexpected = db.getValue(sql"select count(*) from antibot")
assert antibotexpected == $antibotcount
test "table":
let res = query:
select person(id, name, password, email, salt, status)
check res == persondata
test "all_column":
let res = query:
select person(_)
check res.mapIt((it.id, it.name, it.password, it.email, it.salt, it.status)) == persondata
test "column_alias":
let res = query:
select person(id as personid, name as personname)
check res == persondata.mapIt((personid: it.id, personname: it.name))
test "arithmetic":
let res = query:
select person(id * 4 / 2 + 2 - 1 as id)
check res == persondata.mapIt(int(it.id * 4 / 2 + 2 - 1))
test "comparison":
let id = 1
let res = query:
select person(id, name, password, email, salt, status)
where id == ?id
check res == [persondata[id - 1]]
test "comparison_ne":
let id = 1
let res = query:
select person(id, name, password, email, salt, status)
where id != ?id
check res == persondata.filterIt(it.id != id)
test "comparison_ge":
let id = 3
let res = query:
select person(id, name, password, email, salt, status)
where id >= ?id
check res == persondata.filterIt(it.id >= id)
test "comparison_le":
let id = 3
let res = query:
select person(id, name, password, email, salt, status)
where id <= ?id
check res == persondata.filterIt(it.id <= id)
test "comparison_gt":
let id = 3
let res = query:
select person(id, name, password, email, salt, status)
where id > ?id
check res == persondata.filterIt(it.id > id)
test "comparison_lt":
let id = 3
let res = query:
select person(id, name, password, email, salt, status)
where id < ?id
check res == persondata.filterIt(it.id < id)
test "logical_not":
let id = 3
let res = query:
select person(id, name, password, email, salt, status)
where not (id > ?id)
check res == persondata.filterIt(it.id <= id)
test "logical_and":
let
id1 = 2
id2 = 4
let res = query:
select person(id, name, password, email, salt, status)
where id >= ?id1 and id <= ?id2
check res == persondata.filterIt(it.id >= id1 and it.id <= id2)
test "logical_or":
let
id1 = 2
id2 = 4
let res = query:
select person(id, name, password, email, salt, status)
where id == ?id1 or id == ?id2
check res == persondata.filterIt(it.id == id1 or it.id == id2)
test "logical_complex":
let
id1 = 2
id2 = 3
id3 = 4
let res = query:
select person(id, name, password, email, salt, status)
where id > ?id2 and (id == ?id1 or id == ?id3)
check res == persondata.filterIt(it.id == id3)
test "predicate_in":
let
id1 = 2
id2 = 4
let res = query:
select person(id, name, password, email, salt, status)
where id in ?id1 .. ?id2
check res == persondata.filterIt(it.id in id1..id2)
test "predicate_not_in":
let
id1 = 2
id2 = 4
let res = query:
select person(id, name, password, email, salt, status)
where id notin ?id1 .. ?id2
check res == persondata.filterIt(it.id < id1 or it.id > id2)
test "predicate_like":
let pattern = "john1%"
let res = query:
select person(id, name)
where name `like` ?pattern
check res == persondata.filterIt(it.name.startsWith("john1")).mapIt((it.id, it.name))
test "predicate_ilike":
let pattern = "JOHN1%"
let res = query:
select person(id, name)
where name `ilike` ?pattern
check res == persondata.filterIt(it.name.startsWith("john1")).mapIt((it.id, it.name))
test "predicate_not_like":
let pattern = "john1%"
let res = query:
select person(id, name)
where not (name `like` ?pattern)
check res == persondata.filterIt(not it.name.startsWith("john1")).mapIt((it.id, it.name))
test "distinct":
var res = query:
select `distinct` post(author)
res.sort()
let expected = postdata.mapIt(it.author).deduplicate().sortedByIt(it)
check res == expected
test "count_distinct":
let res = query:
select post(count(distinct author))
check res == @[postdata.mapIt(it.author).deduplicate().len]
test "window_row_number":
let res = query:
select post(author, id, over(row_number(), partitionby(author), orderby(id)) as rn)
orderby author, id
var expected: seq[(int, int, int)] = @[]
var counters = initCountTable[int]()
for p in postdata.sortedByIt((it.author, it.id)):
counters.inc(p.author)
expected.add((p.author, p.id, counters[p.author]))
check res == expected
test "window_running_sum":
let res = query:
select post(author, id, over(sum(id), partitionby(author), orderby(id)) as running)
orderby author, id
var expected: seq[(int, int, int)] = @[]
var sums = initTable[int, int]()
for p in postdata.sortedByIt((it.author, it.id)):
sums[p.author] = sums.getOrDefault(p.author) + p.id
expected.add((p.author, p.id, sums[p.author]))
check res == expected
test "limit":
let id = 1
let res = query:
select person(id, name, password, email, salt, status)
limit 1
static:
echo "LIMIT TEST: ", typeof(res)
check res == persondata[id - 1]
test "offset":
let res = query:
select person(id, name)
limit 2
offset 2
check res == persondata[2..3].mapIt((it.id, it.name))
test "match_assignment":
let id = 1
let (name, password, email, salt, status) = query:
select person(name, password, email, salt, status)
where id == ?id
limit 1
check:
name == persondata[id - 1].name
password == persondata[id - 1].password
email == persondata[id - 1].email
salt == persondata[id - 1].salt
status == persondata[id - 1].status
test "groupby":
let res = query:
select post(author, count(id))
groupby author
let counttable = postdata.mapIt(it.author).toCountTable()
for (author, c) in res:
check counttable[author] == c
test "groupby_aggregate":
let author = 3
let res = query:
select post(count(id))
where author == ?author
groupby author
let c = postdata.mapIt(it.author).toCountTable()[author]
check res == [c]
test "orderby":
let res = query:
select person(id, name)
orderby id
check res == persondata.mapIt((it.id, it.name)).sortedByIt(it[0])
test "orderby_asc":
let res = query:
select person(id, name)
orderby asc(id)
check res == persondata.mapIt((it.id, it.name)).sortedByIt(it[0])
test "orderby_desc":
let res = query:
select person(id, name)
orderby desc(id)
let expected = persondata.mapIt((it.id, it.name))
.sorted((x, y) => system.cmp(x[0], y[0]), Descending)
check res == expected
test "orderby_key_select":
let res = query:
select person(id as personid, name)
orderby personid
check res == persondata.mapIt((it.id, it.name)).sortedByIt(it[0])
test "orderby_key_not_select":
let res = query:
select person(name)
orderby id
check res == persondata.sortedByIt(it.id).mapIt(it.name)
test "orderby_mulitple":
# test fix #30 Incorrect handling of multiple sort keys in orderby
let res = query:
select post(author, id)
orderby author, desc(id)
check res == postdata.mapIt((it.author, it.id))
.sorted((x, y) => system.cmp(x[1], y[1]), Descending)
.sortedByIt(it[0])
test "having":
let id = 4
let res = query:
select post(author, count(_) as count)
groupby author
having author == ?id
let expected = collect(newSeq):
for p in postdata.mapIt(it.author).toCountTable().pairs():
if p[0] == id: p
check res == expected
test "having_aggregate":
let countvalue = 2
let res = query:
select post(author, count(id) as count)
groupby author
having count(id) >= ?countvalue
let expected = collect(newSeq):
for p in postdata.mapIt(it.author).toCountTable().pairs():
if p[1] >= countvalue: p
check res.sortedByIt(it[0]) == expected.sortedByIt(it[0])
test "having_complex":
let
authorid1 = 1
authorid2 = 3
countvalue = 2
let res = query:
select post(author, count(id) as count)
groupby author
having count(id) >= ?countvalue and (author == ?authorid1 or author == ?authorid2)
let expected = collect(newSeq):
for p in postdata.mapIt(it.author).toCountTable().pairs():
if p[0] in [authorid1, authorid2] and p[1] >= countvalue: p
check res == expected
test "subquery":
let res = query:
select post(id)
where author in
(select person(id))
check res.sortedByIt(it) == postdata.mapIt(it.id)
test "subquery_nest2":
let
personid1 = 1
personid2 = 2
expectedpost = postdata.filterIt(it.author in [personid1, personid2])
.mapIt(it.id)
let res = query:
select post(id)
where author in
(select person(id) where id == ?personid1 or id == ?personid2)
check res.sortedByIt(it) == expectedpost.sortedByIt(it)
test "subquery_nest3":
var res = query:
select thread(id)
where id in (select post(thread) where author in
(select person(id) where id in {1, 2}))
res.sort()
check res == postdata.filterIt(it.author in [1, 2])
.mapIt(it.thread)
.sortedByIt(it)
test "subquery_having":
# feature for having in subquery
let id = 4
let res = query:
select person(id)
where id in (
select post(author) groupby author having author == ?id)
check res == @[id]
test "exists":
let authorid = postdata[0].author
let res = query:
select person(id)
where exists(select post(id) where author == ?authorid)
check res.sortedByIt(it) == persondata.mapIt(it.id)
test "not_exists":
let missingAuthor = personcount + postcount
let res = query:
select person(id)
where not exists(select post(id) where author == ?missingAuthor)
check res.sortedByIt(it) == persondata.mapIt(it.id)
test "with_cte":
let res = query:
with recent(select post(id, author) where id <= 3)
select recent(author)
orderby id
check res == postdata.filterIt(it.id <= 3).sortedByIt(it.id).mapIt(it.author)
test "with_cte_chain":
let res = query:
with recent(select post(id, author) where id <= 3)
with authors(select recent(author))
select authors(author)
check res.sortedByIt(it) == postdata.filterIt(it.id <= 3).mapIt(it.author).sortedByIt(it)
test "with_cte_subquery":
let res = query:
with recent(select post(id, author) where id <= 3)
select person(id)
where id in (select recent(author))
check res.sortedByIt(it) == postdata.filterIt(it.id <= 3).mapIt(it.author).deduplicate().sortedByIt(it)
test "union":
var res = query:
select person(id) where id <= 2
union
select person(id) where id >= 4
res.sort()
check res == @[1, 2, 4, 5]
res = query:
union(
select person(id) where id <= 2,
select person(id) where id >= 4
)
res.sort()
check res == @[1, 2, 4, 5]
test "intersect":
var res = query:
select person(id) where id <= 3
intersect
select person(id) where id >= 2
res.sort()
check res == @[2, 3]
res = query:
intersect(
select person(id) where id <= 3,
select person(id) where id >= 2
)
res.sort()
check res == @[2, 3]
test "except":
var res = query:
select person(id) where id <= 4
`except`
select person(id) where id in {2, 3}
res.sort()
check res == @[1, 4]
res = query:
`except`(
select person(id) where id <= 4,
select person(id) where id in {2, 3}
)
res.sort()
check res == @[1, 4]
test "complex_query_subquery_having":
let
id1 = 2
id2 = 3
let res = query:
select thread(count(_))
where id in (
select post(thread) where (author == ?id1 or author == ?id2) and id in (
select post(min(id)) groupby thread having min(id) > 3))
limit 1
let threadinpost = postdata.mapIt(it.thread).deduplicate().sortedByIt(it)
var postminids: seq[int]
for id in threadinpost:
let group = collect(newSeq):
for p in postdata:
if p.thread == id: p.id
postminids.add(group.min())
let threadids = postdata.filterIt(it.author in [id1, id2] and
it.id in postminids.filterIt(it > 3))
.mapIt(it.thread)
check res == threadids.len()
test "auto_join":
let postid = 1
let res = query:
select post(author)
join person(name)
where id == ?postid
let (author, name) = res[0]
check name == persondata[author - 1].name
test "join_on":
# test fix #29 Cannot handle join on condition correctly
let postid = 1
let res = query:
select post(author)
join person(name) on author == id
where id == ?postid
let (author, name) = res[0]
check name == persondata[author - 1].name
test "leftjoin_on":
let postid = 1
let res = query:
select post(author)
leftjoin person(name) on author == id
where id == ?postid
let (author, name) = res[0]
check name == persondata[author - 1].name
test "leftouterjoin_on":
let postid = 1
let res = query:
select post(author)
leftouterjoin person(name) on author == id
where id == ?postid
let (author, name) = res[0]
check name == persondata[author - 1].name
test "casewhen":
# need more test, only number
# has problem with string or expression in condition
let res = query:
select person(name, (if id == 1: 10 elif id == 2: 20 else: 30) as pid)
check res == persondata.mapIt((
name: it.name,
pid: if it.id == 1: 10 elif it.id == 2: 20 else: 30))
test "produce_json":
# test fix #27 Error: type mismatch: got <typeof(nil)>
let threadjson = query:
select thread(id, name)
produce json
let expected = threaddata.map do (it: tuple) -> JsonNode:
result = newJObject()
result["id"] = %it.id
result["name"] = %it.name
check threadjson == %expected
test "produce_nim":
let threadnim = query:
select thread(id, name)
produce nim
check threadnim == threaddata.mapIt((it.id, it.name))
test "tryquery":
# unknow use, only not raise dbError?
let res = tryQuery:
select thread(id)
check res == threaddata.mapIt(it.id)
test "createproc":
createProc getAllThreadIds:
select thread(id)
check db.getAllThreadIds() == threaddata.mapIt(it.id)
test "createiter":
createIter allThreadIdsIter:
select thread(id)
let res = collect(newSeq):
for id in db.allThreadIdsIter:
id
check res.sortedByIt(it) == threaddata.mapIt(it.id)
test "update_concat_op":
let
id = 3
name = persondata[id - 1].name
appendstr = "updated"
nameupdated = name & appendstr
query:
update person(name = name & ?appendstr)
where id == ?id
let res = query:
select person(name)
where id == ?id
check res[0] == nameupdated
test "delete_one":
# test fix #14: delete not return value
let id = 1
query:
delete antibot
where id == ?id
let res = query:
select antibot(_)
where id == ?id
check res == []
test "delete_all":
query:
delete antibot
let res = query:
select antibot(_)
check res == []
test "insert_return_id":
# test fix #28 returning id fail under postgresql
let expectedid = 6
let id = query:
insert antibot(id = ?expectedid, ip = "", answer = "just insert")
returning id
check id == expectedid
test "insert_return_answer":
# test returning non-id parameter
let expectedanswer = "just insert another"
let answer = query:
insert antibot(id = 9, ip = "", answer = ?expectedanswer)
returning answer
check answer == expectedanswer
test "insert_return_id_auto":
# test returning id column
when defined(postgre):
# fix postgres sequence so next nextval returns 10
discard db.getValue(sql"select setval('antibot_id_seq', ?, ?)", 10, false)
let answer = query:
insert antibot(ip = "", answer = "just auto insert")
returning id
check answer == 10
test "insert_returning_uuid":
# test returning uuid column
let expecteduuid = "123e4567-e89b-12d3-a456-426614174000"
let uuid = query:
insert error(uuid = ?expecteduuid, message = "just insert")
returning uuid
check uuid == expecteduuid
test "where_json":
let
id = 1
p = %*{"id": id}
let res = query:
select person(id)
where id == %p["id"]
check res == [id]
test "insert_json":
let
id = 7
answer = "json answer"
a = %*{"answer": answer}
query:
insert antibot(id = ?id, ip = "", answer = %a["answer"])
let res = query:
select antibot(answer)
where id == ?id
check res[0] == answer
test "update_json":
let
id = 3
name = "json"
p = %*{"name": name}
query:
update person(name = %p["name"])
where id == ?id
let res = query:
select person(name)
where id == ?id
check res[0] == name
test "insert_rawsql":
let id = 8
query:
insert antibot(id = ?id, ip = "", answer = !!"'raw sql'",
created = !!"CURRENT_TIMESTAMP")
let res = query:
select antibot(_)
where id == ?id
check res[0].answer == "raw sql"
test "update_rawsql":
let id = 3
let res = query:
select person(name)
where id == ?id
query:
update person(name = !!"UPPER(name)")
where id == ?id
let res2 = query:
select person(name)
where id == ?id
check res[0] != res2[0]
check res[0].toUpper() == res2[0]
test "empty_string":
db.exec(insertthread, 6, "", 77)
createProc getAllThreads:
select thread(id, name, views)
where id >= 5
let rows = db.getAllThreads
check rows[1].id == 6
check rows[1].views == 77
check rows[1].name == ""