From e954fdefec4a9cf2b8ad9b4c10e19f25ca430ef8 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Mon, 8 Apr 2024 16:22:26 +0100 Subject: [PATCH] SQL: decompose SQL statements and assert errors --- README.md | 9 ++++- sql.prs | 5 ++- src/postgre_actor.nim | 56 +++++++++++++++++++------- src/schema/sql.nim | 10 +++-- src/sqlite_actor.nim | 89 +++++++++++++++++++++++++++++------------- syndicate_utils.nimble | 2 +- 6 files changed, 122 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index e962f7d..901c9fa 100644 --- a/README.md +++ b/README.md @@ -208,8 +208,13 @@ let ?tuplespace = dataspace $sqlspace -$tuplespace ? [?id ?name] [ - $log ! }> +$tuplespace [ + ? [?id ?name] [ + $log ! }> + ] + ? [ + $log ! + ] ] ``` diff --git a/sql.prs b/sql.prs index 17b9c89..ae33703 100644 --- a/sql.prs +++ b/sql.prs @@ -2,4 +2,7 @@ version 1 . # When asserted the actor reponds to @target rows as records # of the given label and row columns as record fields. -Query = . +Query = . + +# When a query fails this is asserted instead. +SqlError = . diff --git a/src/postgre_actor.nim b/src/postgre_actor.nim index e320bfe..c9e6263 100644 --- a/src/postgre_actor.nim +++ b/src/postgre_actor.nim @@ -87,6 +87,24 @@ proc splitParams(params: StringPairs): (cstringArray, cstringArray) = for i, _ in params: strings[i] = params[i][1] result[1] = allocCStringArray(strings) +proc renderSql(tokens: openarray[Value]): string = + for token in tokens: + if result.len > 0: result.add ' ' + case token.kind + of pkSymbol: + result.add token.symbol.string + of pkString: + result.add '\'' + result.add token.string + result.add '\'' + of pkFloat, pkRegister, pkBigInt: + result.add $token + of pkBoolean: + if token.bool: result.add '1' + else: result.add '0' + else: + return "" + proc spawnPostgreActor*(turn: var Turn; root: Cap): Actor {.discardable.} = spawn("postgre", turn) do (turn: var Turn): during(turn, root, ?:PostgreArguments) do (params: StringPairs, ds: Cap): @@ -102,21 +120,29 @@ proc spawnPostgreActor*(turn: var Turn; root: Cap): Actor {.discardable.} = statusHandle = publish(turn, ds, initRecord("status", toSymbol($status), msg.toPreserves)) if status == CONNECTION_OK: - during(turn, ds, ?:Query) do (statement: string, target: Cap): - var res = PQexec(conn, statement) - var st = PQresultStatus(res) - discard publish(turn, ds, toRecord( - "error", statement, toSymbol($PQresStatus(st)), $PQresultErrorMessage(res))) - if st == PGRES_TUPLES_OK or st == PGRES_SINGLE_TUPLE: - let tuples = PQntuples(res) - let fields = PQnfields(res) - if tuples > 0 and fields > 0: - for r in 0.. 0 and fields > 0: + for r in 0.. 0: result.add ' ' + case token.kind + of pkSymbol: + result.add token.symbol.string + of pkString: + result.add '\'' + result.add token.string + result.add '\'' + of pkFloat, pkRegister, pkBigInt: + result.add $token + of pkBoolean: + if token.bool: result.add '1' + else: result.add '0' + else: + return "" + proc spawnSqliteActor*(turn: var Turn; root: Cap): Actor {.discardable.} = spawn("sqlite-actor", turn) do (turn: var Turn): during(turn, root, ?:SqliteArguments) do (path: string, ds: Cap): - stderr.writeLine("opening SQLite database ", path) - var db: Sqlite3 - if open_v2(path, addr db, SQLITE_OPEN_READONLY, nil) != SQLITE_OK: - logError(db, path) - else: - during(turn, ds, ?:Query) do (statement: string, target: Cap): - var stmt: Stmt - if prepare_v2(db, statement, statement.len.cint, addr stmt, nil) != SQLITE_OK: - logError(db, statement) - else: - try: - let arity = column_count(stmt) - var res = step(stmt) - while res == SQLITE_ROW: - var rec = extractTuple(stmt, arity) - discard publish(turn, target, rec) - res = step(stmt) - assert res != 100 - if res != SQLITE_DONE: - logError(db, statement) - finally: - if finalize(stmt) != SQLITE_OK: logError(db, statement) - do: - close(db) - stderr.writeLine("closed SQLite database ", path) + linkActor(turn, path) do (turn: var Turn): + let facet = turn.facet + stderr.writeLine("opening SQLite database ", path) + var db: Sqlite3 + if open_v2(path, addr db, SQLITE_OPEN_READONLY, nil) != SQLITE_OK: + assertError(facet, ds, db, path) + else: + turn.onStop do (turn: var Turn): + close(db) + stderr.writeLine("closed SQLite database ", path) + during(turn, ds, ?:Query) do (statement: seq[Value], target: Cap): + var + stmt: Stmt + text = renderSql statement + if text == "": + assertError(facet, target, "invalid statement", $statement) + elif prepare_v2(db, text, text.len.cint, addr stmt, nil) != SQLITE_OK: + assertError(facet, target, db, text) + else: + try: + let arity = column_count(stmt) + var res = step(stmt) + while res == SQLITE_ROW: + var rec = extractTuple(stmt, arity) + discard publish(turn, target, rec) + res = step(stmt) + assert res != 100 + if res != SQLITE_DONE: + assertError(facet, target, db, text) + finally: + if finalize(stmt) != SQLITE_OK: assertError(facet, target, db, text) when isMainModule: import syndicate/relays diff --git a/syndicate_utils.nimble b/syndicate_utils.nimble index 8bd141b..339db0a 100644 --- a/syndicate_utils.nimble +++ b/syndicate_utils.nimble @@ -1,6 +1,6 @@ # Package -version = "20240407" +version = "20240408" author = "Emery Hemingway" description = "Utilites for Syndicated Actors and Synit" license = "unlicense"