feat: add forum Nim client improvements and SQLite adaptor
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
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
## BaraDB adapter mimicking db_sqlite API for NimForum
|
||||
|
||||
import std/strutils, std/tables, std/sequtils, std/parseutils
|
||||
import ../../clients/nim/src/baradb/client as baradb_client
|
||||
|
||||
type
|
||||
DbError* = object of CatchableError
|
||||
SqlQuery* = distinct string
|
||||
DbConn* = distinct pointer
|
||||
|
||||
proc sql*(query: string): SqlQuery =
|
||||
SqlQuery(query)
|
||||
|
||||
proc dbError*(msg: string) {.noreturn.} =
|
||||
var e: ref DbError
|
||||
new(e)
|
||||
e.msg = msg
|
||||
raise e
|
||||
|
||||
proc dbQuote*(s: string): string =
|
||||
result = "'"
|
||||
for c in items(s):
|
||||
if c == '\'': add(result, "''")
|
||||
else: add(result, c)
|
||||
add(result, '\'')
|
||||
|
||||
proc dbFormat*(formatstr: SqlQuery, args: varargs[string]): string =
|
||||
var res = ""
|
||||
var a = 0
|
||||
for c in items(string(formatstr)):
|
||||
if c == '?':
|
||||
if a == args.len:
|
||||
dbError("""The number of \"?\" given exceeds the number of parameters present in the query.""")
|
||||
add(res, dbQuote(args[a]))
|
||||
inc(a)
|
||||
else:
|
||||
add(res, c)
|
||||
res
|
||||
|
||||
proc toClient(db: DbConn): SyncClient =
|
||||
cast[SyncClient](db)
|
||||
|
||||
proc open*(connection, user, password, database: string): DbConn =
|
||||
var config = defaultConfig()
|
||||
if connection.contains(':'):
|
||||
let parts = connection.split(':')
|
||||
config.host = parts[0]
|
||||
config.port = parseInt(parts[1])
|
||||
elif connection.len > 0 and not connection.endsWith(".db"):
|
||||
config.host = connection
|
||||
config.database = database
|
||||
config.username = user
|
||||
config.password = password
|
||||
var client = newSyncClient(config)
|
||||
client.connect()
|
||||
GC_ref(client)
|
||||
return cast[DbConn](client)
|
||||
|
||||
proc close*(db: DbConn) =
|
||||
let client = toClient(db)
|
||||
baradb_client.close(client)
|
||||
GC_unref(client)
|
||||
|
||||
proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) =
|
||||
let client = toClient(db)
|
||||
let q = dbFormat(query, args)
|
||||
discard client.query(q)
|
||||
|
||||
proc tryExec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): bool =
|
||||
try:
|
||||
let client = toClient(db)
|
||||
let q = dbFormat(query, args)
|
||||
discard client.query(q)
|
||||
return true
|
||||
except:
|
||||
return false
|
||||
|
||||
proc getRow*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): seq[string] =
|
||||
let client = toClient(db)
|
||||
let q = dbFormat(query, args)
|
||||
let qr = client.query(q)
|
||||
if qr.rows.len > 0:
|
||||
return qr.rows[0]
|
||||
else:
|
||||
return newSeq[string](qr.columns.len)
|
||||
|
||||
proc getAllRows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): seq[seq[string]] =
|
||||
let client = toClient(db)
|
||||
let q = dbFormat(query, args)
|
||||
let qr = client.query(q)
|
||||
return qr.rows
|
||||
|
||||
proc getValue*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): string =
|
||||
let row = getRow(db, query, args)
|
||||
if row.len > 0: return row[0]
|
||||
return ""
|
||||
|
||||
iterator fastRows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): seq[string] =
|
||||
let client = toClient(db)
|
||||
let q = dbFormat(query, args)
|
||||
let qr = client.query(q)
|
||||
for row in qr.rows:
|
||||
yield row
|
||||
|
||||
iterator rows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): seq[string] =
|
||||
for r in fastRows(db, query, args): yield r
|
||||
|
||||
proc insertID*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): int64 =
|
||||
let client = toClient(db)
|
||||
let q = dbFormat(query, args)
|
||||
discard client.query(q)
|
||||
let sqlStr = string(query).strip().toLower()
|
||||
var tableName = ""
|
||||
if sqlStr.startsWith("insert into "):
|
||||
let rest = sqlStr[12..^1]
|
||||
let spacePos = rest.find(' ')
|
||||
if spacePos > 0:
|
||||
tableName = rest[0..<spacePos]
|
||||
if tableName.len > 0:
|
||||
let idQr = client.query("SELECT max(id) FROM " & tableName)
|
||||
if idQr.rows.len > 0 and idQr.rows[0].len > 0:
|
||||
try:
|
||||
return parseInt(idQr.rows[0][0]).int64
|
||||
except:
|
||||
discard
|
||||
return -1
|
||||
|
||||
proc tryInsertID*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): int64 =
|
||||
try:
|
||||
return insertID(db, query, args)
|
||||
except:
|
||||
return -1
|
||||
|
||||
proc nextId*(db: DbConn, tableName: string): int64 =
|
||||
let client = toClient(db)
|
||||
let qr = client.query("SELECT max(id) FROM " & tableName)
|
||||
if qr.rows.len > 0 and qr.rows[0].len > 0:
|
||||
try:
|
||||
let current = parseInt(qr.rows[0][0])
|
||||
return current.int64 + 1
|
||||
except:
|
||||
return 1
|
||||
return 1
|
||||
|
||||
proc execAffectedRows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): int64 =
|
||||
let client = toClient(db)
|
||||
let q = dbFormat(query, args)
|
||||
let qr = client.query(q)
|
||||
return qr.affectedRows.int64
|
||||
@@ -0,0 +1,21 @@
|
||||
import baradb_sqlite
|
||||
|
||||
var db = open("127.0.0.1:9472", "", "", "default")
|
||||
|
||||
db.exec(sql"CREATE TABLE test_adapter (id INT PRIMARY KEY, name STRING)")
|
||||
db.exec(sql"INSERT INTO test_adapter (id, name) VALUES (1, 'hello')")
|
||||
db.exec(sql"INSERT INTO test_adapter (id, name) VALUES (2, 'world')")
|
||||
|
||||
let rows = db.getAllRows(sql"SELECT * FROM test_adapter")
|
||||
echo "All rows: ", rows
|
||||
|
||||
let row = db.getRow(sql"SELECT * FROM test_adapter WHERE id = 1")
|
||||
echo "Row 1: ", row
|
||||
|
||||
let val = db.getValue(sql"SELECT name FROM test_adapter WHERE id = 2")
|
||||
echo "Value: ", val
|
||||
|
||||
let cnt = db.getValue(sql"SELECT count(*) FROM test_adapter")
|
||||
echo "Count: ", cnt
|
||||
|
||||
db.close()
|
||||
@@ -108,6 +108,16 @@ proc readString(buf: openArray[byte], pos: var int): string =
|
||||
result[i] = char(buf[pos + i])
|
||||
pos += len
|
||||
|
||||
proc toBytes(s: string): seq[byte] =
|
||||
result = newSeq[byte](s.len)
|
||||
for i, c in s:
|
||||
result[i] = byte(c)
|
||||
|
||||
proc toString(s: seq[byte]): string =
|
||||
result = newString(s.len)
|
||||
for i, b in s:
|
||||
result[i] = char(b)
|
||||
|
||||
proc serializeValue*(buf: var seq[byte], val: WireValue) =
|
||||
buf.add(byte(val.kind))
|
||||
case val.kind
|
||||
@@ -289,7 +299,7 @@ proc close*(client: BaraClient) =
|
||||
if client.connected:
|
||||
try:
|
||||
let msg = buildMessage(mkClose, client.nextId(), @[])
|
||||
waitFor client.socket.send(cast[string](msg))
|
||||
waitFor client.socket.send(toString(msg))
|
||||
except: discard
|
||||
client.socket.close()
|
||||
client.connected = false
|
||||
@@ -319,13 +329,13 @@ proc readQueryResponse(client: BaraClient): Future[QueryResult] {.async.} =
|
||||
raise newException(IOError, "Connection closed")
|
||||
|
||||
var pos = 0
|
||||
let hdrData = cast[seq[byte]](headerData)
|
||||
let hdrData = toBytes(headerData)
|
||||
let kind = MsgKind(readUint32(hdrData, pos))
|
||||
let payloadLen = int(readUint32(hdrData, pos))
|
||||
discard readUint32(hdrData, pos)
|
||||
|
||||
let payloadStr = await client.socket.recv(payloadLen)
|
||||
var payload = cast[seq[byte]](payloadStr)
|
||||
var payload = toBytes(payloadStr)
|
||||
|
||||
result = QueryResult(columns: @[], rows: @[], rowCount: 0, affectedRows: 0)
|
||||
|
||||
@@ -360,14 +370,14 @@ proc readQueryResponse(client: BaraClient): Future[QueryResult] {.async.} =
|
||||
let compHeader = await client.socket.recv(12)
|
||||
if compHeader.len >= 12:
|
||||
var chPos = 0
|
||||
let chData = cast[seq[byte]](compHeader)
|
||||
let chData = toBytes(compHeader)
|
||||
let compKind = MsgKind(readUint32(chData, chPos))
|
||||
let compLen = int(readUint32(chData, chPos))
|
||||
discard readUint32(chData, chPos)
|
||||
let compPayloadStr = await client.socket.recv(compLen)
|
||||
if compKind == mkComplete:
|
||||
var cpPos = 0
|
||||
result.affectedRows = int(readUint32(cast[seq[byte]](compPayloadStr), cpPos))
|
||||
result.affectedRows = int(readUint32(toBytes(compPayloadStr), cpPos))
|
||||
return
|
||||
if kind == mkComplete:
|
||||
var rpos = 0
|
||||
@@ -379,7 +389,8 @@ proc query*(client: BaraClient, sql: string): Future[QueryResult] {.async.} =
|
||||
raise newException(IOError, "Not connected")
|
||||
|
||||
let msg = makeQueryMessage(client.nextId(), sql)
|
||||
await client.socket.send(cast[string](msg))
|
||||
let msgStr = toString(msg)
|
||||
await client.socket.send(msgStr)
|
||||
|
||||
return await client.readQueryResponse()
|
||||
|
||||
@@ -388,7 +399,8 @@ proc query*(client: BaraClient, sql: string, params: seq[WireValue]): Future[Que
|
||||
raise newException(IOError, "Not connected")
|
||||
|
||||
let msg = makeQueryParamsMessage(client.nextId(), sql, params)
|
||||
await client.socket.send(cast[string](msg))
|
||||
let msgStr = toString(msg)
|
||||
await client.socket.send(msgStr)
|
||||
|
||||
return await client.readQueryResponse()
|
||||
|
||||
@@ -401,14 +413,15 @@ proc auth*(client: BaraClient, token: string) {.async.} =
|
||||
raise newException(IOError, "Not connected")
|
||||
|
||||
let msg = makeAuthMessage(client.nextId(), token)
|
||||
await client.socket.send(cast[string](msg))
|
||||
let msgStr = toString(msg)
|
||||
await client.socket.send(msgStr)
|
||||
|
||||
let headerData = await client.socket.recv(12)
|
||||
if headerData.len < 12:
|
||||
raise newException(IOError, "Connection closed")
|
||||
|
||||
var pos = 0
|
||||
let hdrData = cast[seq[byte]](headerData)
|
||||
let hdrData = toBytes(headerData)
|
||||
let kind = MsgKind(readUint32(hdrData, pos))
|
||||
let payloadLen = int(readUint32(hdrData, pos))
|
||||
discard readUint32(hdrData, pos)
|
||||
@@ -418,7 +431,7 @@ proc auth*(client: BaraClient, token: string) {.async.} =
|
||||
elif kind == mkError:
|
||||
let payloadStr = await client.socket.recv(payloadLen)
|
||||
var epos = 0
|
||||
let emsg = readString(cast[seq[byte]](payloadStr), epos)
|
||||
let emsg = readString(toBytes(payloadStr), epos)
|
||||
raise newException(IOError, "Auth failed: " & emsg)
|
||||
else:
|
||||
raise newException(IOError, "Unexpected auth response: 0x" & toHex(uint32(kind), 2))
|
||||
@@ -427,14 +440,15 @@ proc ping*(client: BaraClient): Future[bool] {.async.} =
|
||||
if not client.connected:
|
||||
return false
|
||||
let msg = buildMessage(mkPing, client.nextId(), @[])
|
||||
await client.socket.send(cast[string](msg))
|
||||
let msgStr = toString(msg)
|
||||
await client.socket.send(msgStr)
|
||||
|
||||
let headerData = await client.socket.recv(12)
|
||||
if headerData.len < 12:
|
||||
return false
|
||||
|
||||
var pos = 0
|
||||
let hdrData = cast[seq[byte]](headerData)
|
||||
let hdrData = toBytes(headerData)
|
||||
let kind = MsgKind(readUint32(hdrData, pos))
|
||||
return kind == mkPong
|
||||
|
||||
|
||||
Reference in New Issue
Block a user