diff --git a/adaptors/nim/baradb_sqlite.nim b/adaptors/nim/baradb_sqlite.nim new file mode 100644 index 0000000..83e1685 --- /dev/null +++ b/adaptors/nim/baradb_sqlite.nim @@ -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.. 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 diff --git a/adaptors/nim/test_baradb_adapter.nim b/adaptors/nim/test_baradb_adapter.nim new file mode 100644 index 0000000..08cd323 --- /dev/null +++ b/adaptors/nim/test_baradb_adapter.nim @@ -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() diff --git a/clients/nim/src/baradb/client.nim b/clients/nim/src/baradb/client.nim index 96834d2..9753303 100644 --- a/clients/nim/src/baradb/client.nim +++ b/clients/nim/src/baradb/client.nim @@ -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