# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense import preserves, syndicate import ./schema/[config, sql] # Avoid Sqlite3 from the standard library because it is # only held together by wishful thinking and dlload. {.passC: staticExec("pkg-config --cflags sqlite3").} {.passL: staticExec("pkg-config --libs sqlite3").} {.pragma: sqlite3h, header: "sqlite3.h".} var SQLITE_VERSION_NUMBER {.importc, sqlite3h.}: cint SQLITE_OK {.importc, sqlite3h.}: cint SQLITE_ROW {.importc, sqlite3h.}: cint SQLITE_DONE {.importc, sqlite3h.}: cint SQLITE_OPEN_READONLY {.importc, sqlite3h.}: cint const SQLITE_INTEGER = 1 SQLITE_FLOAT = 2 SQLITE_TEXT = 3 SQLITE_BLOB = 4 # SQLITE_NULL = 5 type Sqlite3 {.importc: "sqlite3", sqlite3h.} = distinct pointer Stmt {.importc: "sqlite3_stmt", sqlite3h.} = distinct pointer {.pragma: importSqlite3, importc: "sqlite3_$1", sqlite3h.} proc libversion_number: cint {.importSqlite3.} proc open_v2(filename: cstring; ppDb: ptr Sqlite3; flags: cint; zVfs: cstring): cint {.importSqlite3.} proc close(ds: Sqlite3): int32 {.discardable, importSqlite3.} proc errmsg(db: Sqlite3): cstring {.importSqlite3.} proc prepare_v2(db: Sqlite3; zSql: cstring, nByte: cint; ppStmt: ptr Stmt; pzTail: ptr cstring): cint {.importSqlite3.} proc step(para1: Stmt): cint {.importSqlite3.} proc column_count(stmt: Stmt): int32 {.importSqlite3.} proc column_blob(stmt: Stmt; col: cint): pointer {.importSqlite3.} proc column_bytes(stmt: Stmt; col: cint): cint {.importSqlite3.} proc column_double(stmt: Stmt; col: cint): float64 {.importSqlite3.} proc column_int64(stmt: Stmt; col: cint): int64 {.importSqlite3.} proc column_text(stmt: Stmt; col: cint): cstring {.importSqlite3.} proc column_type(stmt: Stmt; col: cint): cint {.importSqlite3.} proc finalize(stmt: Stmt): cint {.importSqlite3.} doAssert libversion_number() == SQLITE_VERSION_NUMBER proc assertError(facet: Facet; cap: Cap; db: Sqlite3; context: string) = run(facet) do (turn: Turn): publish(turn, cap, SqlError( msg: $errmsg(db), context: context, )) proc assertError(facet: Facet; cap: Cap; msg, context: string) = run(facet) do (turn: Turn): publish(turn, cap, SqlError( msg: msg, context: context, )) proc extractValue(stmt: Stmt; col: cint): Value = case column_type(stmt, col) of SQLITE_INTEGER: result = toPreserves(column_int64(stmt, col)) of SQLITE_FLOAT: result = toPreserves(column_double(stmt, col)) of SQLITE_TEXT: result = Value(kind: pkString, string: newString(column_bytes(stmt, col))) if result.string.len > 0: copyMem(addr result.string[0], column_text(stmt, col), result.string.len) of SQLITE_BLOB: result = Value(kind: pkByteString, bytes: newSeq[byte](column_bytes(stmt, col))) if result.bytes.len > 0: copyMem(addr result.bytes[0], column_blob(stmt, col), result.bytes.len) else: result = initRecord("null") proc extractTuple(stmt: Stmt; arity: cint): Value = result = initSequence(arity) for col 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: Turn; root: Cap): Actor {.discardable.} = spawn("sqlite-actor", turn) do (turn: Turn): during(turn, root, ?:SqliteArguments) do (path: string, ds: Cap): linkActor(turn, path) do (turn: 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: 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 runActor("main") do (turn: Turn): resolveEnvironment(turn) do (turn: Turn; ds: Cap): spawnSqliteActor(turn, ds)