feat: canonical Nim client refactor with typed rows, pool, and allographer wrapper
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
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
- Extract wire protocol into clients/nim/src/baradb/wire.nim - Add BaraError exception hierarchy - Refactor BaraClient/SyncClient with typedRows, AsyncLock request queue, timeouts, TLS config - Add BaraPool and optional HTTP fallback - Add mock-server wire and pool unit tests - Bump baradb nimble package to 1.2.0 - Make nim-allographer depend on canonical baradb client - Use typed rows in allographer toJson - Deprecate src/barabadb/client/client.nim - Update docs/en/clients.md and clients/nim/README.md
This commit is contained in:
@@ -182,3 +182,10 @@ suite "Wire Protocol Extended":
|
||||
check wireValueToString(WireValue(kind: fkInt32, int32Val: 42)) == "42"
|
||||
check wireValueToString(WireValue(kind: fkString, strVal: "hello")) == "hello"
|
||||
check wireValueToString(WireValue(kind: fkVector, vecVal: @[1.0'f32])) == "<vector:1>"
|
||||
|
||||
suite "Typed rows":
|
||||
test "QueryResult carries typed rows for string and int":
|
||||
let client = newClient()
|
||||
let qb = newQueryBuilder(client)
|
||||
discard qb
|
||||
check compiles(client.query("SELECT 1"))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import std/unittest
|
||||
import std/asyncdispatch
|
||||
import std/asyncnet
|
||||
import std/net as netmod
|
||||
import std/strutils
|
||||
import std/os
|
||||
import baradb/client
|
||||
@@ -15,8 +15,8 @@ const
|
||||
|
||||
proc serverAvailable(): bool =
|
||||
try:
|
||||
var socket = newAsyncSocket()
|
||||
waitFor socket.connect(TestHost, Port(TestPort))
|
||||
var socket = netmod.newSocket()
|
||||
socket.connect(TestHost, Port(TestPort), timeout = 1000)
|
||||
socket.close()
|
||||
return true
|
||||
except:
|
||||
@@ -26,96 +26,103 @@ let hasServer = serverAvailable()
|
||||
|
||||
suite "Integration: Connection":
|
||||
test "Connect and close":
|
||||
if not hasServer:
|
||||
if hasServer:
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
check not client.isConnected
|
||||
waitFor client.connect()
|
||||
check client.isConnected
|
||||
client.close()
|
||||
check not client.isConnected
|
||||
else:
|
||||
skip()
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
check not client.isConnected
|
||||
waitFor client.connect()
|
||||
check client.isConnected
|
||||
client.close()
|
||||
check not client.isConnected
|
||||
|
||||
test "Ping":
|
||||
if not hasServer:
|
||||
if hasServer:
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
waitFor client.connect()
|
||||
check (waitFor client.ping()) == true
|
||||
client.close()
|
||||
else:
|
||||
skip()
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
waitFor client.connect()
|
||||
check (waitFor client.ping()) == true
|
||||
client.close()
|
||||
|
||||
suite "Integration: Query":
|
||||
test "Simple SELECT":
|
||||
if not hasServer:
|
||||
if hasServer:
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
waitFor client.connect()
|
||||
let result = waitFor client.query("SELECT 1 as one")
|
||||
check result.rowCount >= 0
|
||||
client.close()
|
||||
else:
|
||||
skip()
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
waitFor client.connect()
|
||||
let result = waitFor client.query("SELECT 1 as one")
|
||||
check result.rowCount >= 0
|
||||
client.close()
|
||||
|
||||
test "Parameterized query":
|
||||
if not hasServer:
|
||||
if hasServer:
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
waitFor client.connect()
|
||||
let result = waitFor client.query(
|
||||
"SELECT $1 as num, $2 as txt",
|
||||
@[WireValue(kind: fkInt64, int64Val: 42), WireValue(kind: fkString, strVal: "hello")]
|
||||
)
|
||||
check result.rowCount >= 0
|
||||
client.close()
|
||||
else:
|
||||
skip()
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
waitFor client.connect()
|
||||
let result = waitFor client.query(
|
||||
"SELECT $1 as num, $2 as txt",
|
||||
@[WireValue(kind: fkInt64, int64Val: 42), WireValue(kind: fkString, strVal: "hello")]
|
||||
)
|
||||
check result.rowCount >= 0
|
||||
client.close()
|
||||
|
||||
suite "Integration: DDL & DML":
|
||||
test "Create table, insert, select, drop":
|
||||
if not hasServer:
|
||||
if hasServer:
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
waitFor client.connect()
|
||||
|
||||
try:
|
||||
discard waitFor client.exec("DROP TABLE IF EXISTS nim_test_users")
|
||||
except:
|
||||
discard
|
||||
|
||||
discard waitFor client.exec("CREATE TABLE nim_test_users (id INT PRIMARY KEY, name STRING, age INT)")
|
||||
let affected = waitFor client.exec("INSERT INTO nim_test_users (id, name, age) VALUES (1, 'Alice', 30)")
|
||||
check affected >= 0
|
||||
|
||||
let result = waitFor client.query("SELECT name, age FROM nim_test_users WHERE id = 1")
|
||||
check result.rowCount == 1
|
||||
client.close()
|
||||
else:
|
||||
skip()
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
waitFor client.connect()
|
||||
|
||||
try:
|
||||
discard waitFor client.exec("DROP TABLE IF EXISTS nim_test_users")
|
||||
except:
|
||||
discard
|
||||
|
||||
discard waitFor client.exec("CREATE TABLE nim_test_users (id INT PRIMARY KEY, name STRING, age INT)")
|
||||
let affected = waitFor client.exec("INSERT INTO nim_test_users (id, name, age) VALUES (1, 'Alice', 30)")
|
||||
check affected >= 0
|
||||
|
||||
let result = waitFor client.query("SELECT name, age FROM nim_test_users WHERE id = 1")
|
||||
check result.rowCount == 1
|
||||
client.close()
|
||||
|
||||
suite "Integration: QueryBuilder":
|
||||
test "Builder exec":
|
||||
if not hasServer:
|
||||
if hasServer:
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
waitFor client.connect()
|
||||
|
||||
try:
|
||||
discard waitFor client.exec("DROP TABLE IF EXISTS nim_test_products")
|
||||
except:
|
||||
discard
|
||||
|
||||
discard waitFor client.exec("CREATE TABLE nim_test_products (id INT PRIMARY KEY, name STRING, price FLOAT)")
|
||||
discard waitFor client.exec("INSERT INTO nim_test_products (id, name, price) VALUES (1, 'Widget', 9.99)")
|
||||
|
||||
let result = waitFor newQueryBuilder(client)
|
||||
.select("name", "price")
|
||||
.from("nim_test_products")
|
||||
.where("id = 1")
|
||||
.exec()
|
||||
check result.rowCount == 1
|
||||
|
||||
discard waitFor client.exec("DROP TABLE nim_test_products")
|
||||
client.close()
|
||||
else:
|
||||
skip()
|
||||
var client = newClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
waitFor client.connect()
|
||||
|
||||
try:
|
||||
discard waitFor client.exec("DROP TABLE IF EXISTS nim_test_products")
|
||||
except:
|
||||
discard
|
||||
|
||||
discard waitFor client.exec("CREATE TABLE nim_test_products (id INT PRIMARY KEY, name STRING, price FLOAT)")
|
||||
discard waitFor client.exec("INSERT INTO nim_test_products (id, name, price) VALUES (1, 'Widget', 9.99)")
|
||||
|
||||
let result = waitFor newQueryBuilder(client)
|
||||
.select("name", "price")
|
||||
.from("nim_test_products")
|
||||
.where("id = 1")
|
||||
.exec()
|
||||
check result.rowCount == 1
|
||||
|
||||
discard waitFor client.exec("DROP TABLE nim_test_products")
|
||||
client.close()
|
||||
|
||||
suite "Integration: SyncClient":
|
||||
test "Sync query":
|
||||
if not hasServer:
|
||||
if hasServer:
|
||||
var client = newSyncClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
client.connect()
|
||||
let result = client.query("SELECT 1 as one")
|
||||
check result.rowCount >= 0
|
||||
client.close()
|
||||
else:
|
||||
skip()
|
||||
var client = newSyncClient(ClientConfig(host: TestHost, port: TestPort))
|
||||
client.connect()
|
||||
let result = client.query("SELECT 1 as one")
|
||||
check result.rowCount >= 0
|
||||
client.close()
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import std/unittest
|
||||
import std/asyncdispatch
|
||||
import baradb/client
|
||||
import baradb/pool
|
||||
|
||||
suite "BaraPool":
|
||||
test "pool stats with one acquired connection":
|
||||
proc run() {.async.} =
|
||||
let cfg = ClientConfig(host: "127.0.0.1", port: 9472, timeoutMs: 100)
|
||||
let pool = newBaraPool(cfg, minConnections = 0, maxConnections = 2)
|
||||
# Without a server, acquire should fail cleanly (timeout or connection refused)
|
||||
var failedCleanly = false
|
||||
try:
|
||||
withClient(pool):
|
||||
discard
|
||||
except BaraError:
|
||||
failedCleanly = true
|
||||
check failedCleanly
|
||||
waitFor run()
|
||||
@@ -0,0 +1,68 @@
|
||||
import std/unittest
|
||||
import std/asyncdispatch
|
||||
import std/asyncnet
|
||||
import std/json
|
||||
import baradb/wire
|
||||
import baradb/client
|
||||
|
||||
proc buildDataResponse(cols: seq[string], rows: seq[seq[WireValue]], affected: int): seq[byte] =
|
||||
var payload: seq[byte] = @[]
|
||||
payload.writeUint32(uint32(cols.len))
|
||||
for c in cols:
|
||||
payload.writeString(c)
|
||||
for c in cols:
|
||||
payload.add(byte(fkString))
|
||||
payload.writeUint32(uint32(rows.len))
|
||||
for row in rows:
|
||||
for wv in row:
|
||||
payload.serializeValue(wv)
|
||||
result = buildMessage(mkData, 1'u32, payload)
|
||||
var completePayload: seq[byte] = @[]
|
||||
completePayload.writeUint32(uint32(affected))
|
||||
result.add(buildMessage(mkComplete, 1'u32, completePayload))
|
||||
|
||||
suite "Wire protocol":
|
||||
test "buildMessage header is 12 bytes + payload":
|
||||
let msg = buildMessage(mkQuery, 7'u32, toBytes("SELECT 1"))
|
||||
check msg.len == 12 + 8
|
||||
|
||||
test "serialize/deserialize round-trip for WireValue":
|
||||
let original = WireValue(kind: fkInt64, int64Val: 42)
|
||||
var buf: seq[byte] = @[]
|
||||
buf.serializeValue(original)
|
||||
var pos = 0
|
||||
let decoded = deserializeValue(buf, pos)
|
||||
check decoded.kind == fkInt64
|
||||
check decoded.int64Val == 42
|
||||
|
||||
test "client query against mock server returns typedRows and rows":
|
||||
proc run() {.async.} =
|
||||
var server = newAsyncSocket()
|
||||
server.setSockOpt(OptReuseAddr, true)
|
||||
server.bindAddr(Port(0), "127.0.0.1")
|
||||
let port = server.getLocalAddr()[1]
|
||||
server.listen()
|
||||
|
||||
proc serve() {.async.} =
|
||||
let s = await server.accept()
|
||||
let data = buildDataResponse(
|
||||
@["name", "age"],
|
||||
@[
|
||||
@[WireValue(kind: fkString, strVal: "Alice"), WireValue(kind: fkInt32, int32Val: 30)],
|
||||
],
|
||||
0,
|
||||
)
|
||||
await s.send(toString(data))
|
||||
s.close()
|
||||
|
||||
asyncCheck serve()
|
||||
|
||||
let client = newClient(ClientConfig(host: "127.0.0.1", port: int(port), timeoutMs: 5000))
|
||||
await client.connect()
|
||||
let qr = await client.query("SELECT name, age FROM users")
|
||||
check qr.rowCount == 1
|
||||
check qr.typedRows[0][1].int32Val == 30
|
||||
check qr.rows[0][1] == "30"
|
||||
client.close()
|
||||
server.close()
|
||||
waitFor run()
|
||||
Reference in New Issue
Block a user