diff --git a/src/tiny_sqlite.nim b/src/tiny_sqlite.nim index b9dce98..8ac1391 100644 --- a/src/tiny_sqlite.nim +++ b/src/tiny_sqlite.nim @@ -13,7 +13,7 @@ when not declared(tupleLen): export options.get, options.isSome, options.isNone type - DbConnImpl = ref object + DbConnImpl = ref object handle: sqlite.Sqlite3 ## The underlying SQLite3 handle cache: StmtCache @@ -66,9 +66,9 @@ const SqliteRcOk = [ sqlite.SQLITE_OK, sqlite.SQLITE_DONE, sqlite.SQLITE_ROW ] # Forward declarations -proc isInTransaction*(db: DbConn): bool {.noSideEffect.} -proc isOpen*(db: DbConn): bool {.noSideEffect, inline.} -proc isAlive*(statement: SqlStatement): bool {.noSideEffect.} +func isInTransaction*(db: DbConn): bool {.raises: [].} +func isOpen*(db: DbConn): bool {.inline, raises: [].} +func isAlive*(statement: SqlStatement): bool {.raises: [].} template handle(db: DbConn): sqlite.Sqlite3 = DbConnImpl(db).handle template handle(statement: SqlStatement): sqlite.Stmt = SqlStatementImpl(statement).handle @@ -105,7 +105,7 @@ proc skipLeadingWhiteSpaceAndComments(sql: var cstring) = let original = sql template `&+`(s: cstring, offset: int): cstring = - cast[cstring](cast[ByteAddress](sql) + offset) + cast[cstring](cast[uint](sql) + offset) while true: case sql[0] @@ -173,7 +173,7 @@ proc toDbValue*[T: type(nil)](val: T): DbValue = proc toDbValues*(values: varargs[DbValue, toDbValue]): seq[DbValue] = ## Convert several values to a sequence of DbValue's. runnableExamples: - doAssert toDbValues("string", 23) == @[toDbValue("string"), toDbValue(23)] + doAssert toDbValues("string", 23) == @[toDbValue("string"), toDbValue(23)] @values proc fromDbValue*(value: DbValue, T: typedesc[Ordinal]): T = @@ -204,9 +204,9 @@ proc fromDbValue*(value: DbValue, T: typedesc[DbValue]): T = ## The purpose of this overload is to do partial unpacking. ## For example, if the type of one column in a result row is unknown, ## the DbValue type can be kept just for that column. - ## + ## ## .. code-block:: nim - ## + ## ## for row in db.iterate("SELECT name, extra FROM Person"): ## # Type of 'extra' is unknown, so we don't unpack it. ## # The 'extra' variable will be of type 'DbValue' @@ -241,7 +241,7 @@ proc `==`*(a, b: DbValue): bool = proc bindParams(db: DbConn, stmtHandle: sqlite.Stmt, params: varargs[DbValue]): Rc = result = sqlite.SQLITE_OK - let expectedParamsLen = sqlite.bind_parameter_count(stmtHandle) + let expectedParamsLen = sqlite.bind_parameter_count(stmtHandle) if expectedParamsLen != params.len: raise newSqliteError("SQL statement contains " & $expectedParamsLen & " parameters but only " & $params.len & " was provided.") @@ -256,7 +256,7 @@ proc bindParams(db: DbConn, stmtHandle: sqlite.Stmt, params: varargs[DbValue]): sqlite.bind_int64(stmtHandle, idx, value.intval) of sqliteReal: sqlite.bind_double(stmtHandle, idx, value.floatVal) - of sqliteText: + of sqliteText: sqlite.bind_text(stmtHandle, idx, value.strVal.cstring, value.strVal.len.int32, sqlite.SQLITE_TRANSIENT) of sqliteBlob: sqlite.bind_blob(stmtHandle, idx.int32, cast[string](value.blobVal).cstring, @@ -266,7 +266,8 @@ proc bindParams(db: DbConn, stmtHandle: sqlite.Stmt, params: varargs[DbValue]): return rc idx.inc -proc prepareSql(db: DbConn, sql: string): sqlite.Stmt = +proc prepareSql(db: DbConn, sql: string): sqlite.Stmt + {.raises: [SqliteError].} = var tail: cstring let rc = sqlite.prepare_v2(db.handle, sql.cstring, sql.len.cint + 1, result, tail) db.checkRc(rc) @@ -338,7 +339,8 @@ iterator iterate(db: DbConn, stmtOrHandle: sqlite.Stmt | SqlStatement, params: v # DbConn # -proc exec*(db: DbConn, sql: string, params: varargs[DbValue, toDbValue]) = +proc exec*(db: DbConn, sql: string, params: varargs[DbValue, toDbValue]) + {.raises: [SqliteError].} = ## Executes ``sql``, which must be a single SQL statement. runnableExamples: let db = openDatabase(":memory:") @@ -361,19 +363,13 @@ template transaction*(db: DbConn, body: untyped) = body else: db.exec("BEGIN") - var ok = true try: - try: - body - except Exception: - ok = false - db.exec("ROLLBACK") - raise + body finally: - if ok: - db.exec("COMMIT") + db.exec(if getCurrentException() != nil: "ROLLBACK" else: "COMMIT") -proc execMany*(db: DbConn, sql: string, params: seq[seq[DbValue]]) = +proc execMany*(db: DbConn, sql: string, params: seq[seq[DbValue]]) + {.raises: [SqliteError].} = ## Executes ``sql``, which must be a single SQL statement, repeatedly using each element of ## ``params`` as parameters. The statements are executed inside a transaction. assertCanUseDb db @@ -381,7 +377,8 @@ proc execMany*(db: DbConn, sql: string, params: seq[seq[DbValue]]) = for p in params: db.exec(sql, p) -proc execScript*(db: DbConn, sql: string) = +proc execScript*(db: DbConn, sql: string) + {.raises: [SqliteError].} = ## Executes ``sql``, which can consist of multiple SQL statements. ## The statements are executed inside a transaction. assertCanUseDb db @@ -399,7 +396,8 @@ proc execScript*(db: DbConn, sql: string) = remaining.skipLeadingWhiteSpaceAndComments() iterator iterate*(db: DbConn, sql: string, - params: varargs[DbValue, toDbValue]): ResultRow = + params: varargs[DbValue, toDbValue]): ResultRow + {.raises: [SqliteError].} = ## Executes ``sql``, which must be a single SQL statement, and yields each result row one by one. assertCanUseDb db let stmtHandle = db.prepareSql(sql, @params) @@ -437,7 +435,7 @@ proc value*(db: DbConn, sql: string, for row in db.iterate(sql, params): return some(row.values[0]) -proc close*(db: DbConn) = +proc close*(db: DbConn) {.raises: [SqliteError].} = ## Closes the database connection. This should be called once the connection will no longer be used ## to avoid leaking memory. Closing an already closed database is a harmless no-op. if not db.isOpen: @@ -480,7 +478,7 @@ proc isReadonly*(db: DbConn): bool = assertCanUseDb db sqlite.db_readonly(db.handle, "main") == 1 -proc isOpen*(db: DbConn): bool {.inline.} = +func isOpen*(db: DbConn): bool {.inline, raises: [].} = ## Returns true if `db` has been opened and not yet closed. runnableExamples: var db: DbConn @@ -491,7 +489,7 @@ proc isOpen*(db: DbConn): bool {.inline.} = doAssert not db.isOpen (not DbConnImpl(db).isNil) and (not db.handle.isNil) -proc isInTransaction*(db: DbConn): bool = +func isInTransaction*(db: DbConn): bool {.raises: [].} = ## Returns true if a transaction is currently active. runnableExamples: let db = openDatabase(":memory:") @@ -517,8 +515,9 @@ proc stmt*(db: DbConn, sql: string): SqlStatement = assertCanUseDb db let handle = prepareSql(db, sql) SqlStatementImpl(handle: handle, db: db).SqlStatement - -proc exec*(statement: SqlStatement, params: varargs[DbValue, toDbValue]) = + +proc exec*(statement: SqlStatement, params: varargs[DbValue, toDbValue]) + {.raises: [SqliteError].} = ## Executes `statement` with `params` as parameters. assertCanUseStatement statement var rc = statement.db.bindParams(statement.handle, params) @@ -530,7 +529,8 @@ proc exec*(statement: SqlStatement, params: varargs[DbValue, toDbValue]) = resetStmt(statement.handle) statement.db.checkRc(rc) -proc execMany*(statement: SqlStatement, params: seq[seq[DbValue]]) = +proc execMany*(statement: SqlStatement, params: seq[seq[DbValue]]) + {.raises: [SqliteError].} = ## Executes ``statement`` repeatedly using each element of ``params`` as parameters. ## The statements are executed inside a transaction. assertCanUseStatement statement @@ -538,7 +538,9 @@ proc execMany*(statement: SqlStatement, params: seq[seq[DbValue]]) = for p in params: statement.exec(p) -iterator iterate*(statement: SqlStatement, params: varargs[DbValue, toDbValue]): ResultRow = +iterator iterate*(statement: SqlStatement, + params: varargs[DbValue, toDbValue]): ResultRow + {.raises: [SqliteError].} = ## Executes ``statement`` and yields each result row one by one. assertCanUseStatement statement var errorRc: int32 @@ -568,7 +570,7 @@ proc one*(statement: SqlStatement, proc value*(statement: SqlStatement, params: varargs[DbValue, toDbValue]): Option[DbValue] = - ## Executes `statement` and returns the first column of the first row found. + ## Executes `statement` and returns the first column of the first row found. ## Returns `none(DbValue)` if no result was found. assertCanUseStatement statement for row in statement.iterate(params): @@ -582,12 +584,15 @@ proc finalize*(statement: SqlStatement): void = discard sqlite.finalize(statement.handle) SqlStatementImpl(statement).handle = nil -proc isAlive*(statement: SqlStatement): bool = +func isAlive*(statement: SqlStatement): bool + {.raises: [].} = ## Returns true if ``statement`` has been initialized and not yet finalized. (not SqlStatementImpl(statement).isNil) and (not statement.handle.isNil) and (not statement.db.handle.isNil) -proc openDatabase*(path: string, mode = dbReadWrite, cacheSize: Natural = 100): DbConn = +proc openDatabase*(path: string, mode = dbReadWrite, + cacheSize: Natural = 100): DbConn + {.raises: [SqliteError].} = ## Open a new database connection to a database file. To create an ## in-memory database the special path `":memory:"` can be used. ## If the database doesn't already exist and ``mode`` is ``dbReadWrite``, @@ -615,7 +620,8 @@ proc openDatabase*(path: string, mode = dbReadWrite, cacheSize: Natural = 100): result.exec("PRAGMA foreign_keys = ON") when not defined(macosx): - proc loadExtension*(db: DbConn, path: string) = + proc loadExtension*(db: DbConn, path: string) + {.raises: [SqliteError].} = ## Load an SQLite extension. Will raise a ``SqliteError`` exception if loading fails. db.checkRc sqlite.db_config(db.handle, sqlite.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, 0); var err: cstring @@ -672,11 +678,11 @@ proc unpack*[T: tuple](row: ResultRow, _: typedesc[T]): T = proc rows*(db: DbConn, sql: string, params: varargs[DbValue, toDbValue]): seq[seq[DbValue]] {.deprecated: "use 'all' instead".} = db.all(sql, params).mapIt(it.values) - + iterator rows*(db: DbConn, sql: string, params: varargs[DbValue, toDbValue]): seq[DbValue] {.deprecated: "use 'iterate' instead".} = for row in db.all(sql, params): yield row.values proc unpack*[T: tuple](row: seq[DbValue], _: typedesc[T]): T {.deprecated.} = - ResultRow(values: row).unpack(T) \ No newline at end of file + ResultRow(values: row).unpack(T) diff --git a/src/tiny_sqlite/sqlite_wrapper.nim b/src/tiny_sqlite/sqlite_wrapper.nim index 4c0c690..762de6c 100644 --- a/src/tiny_sqlite/sqlite_wrapper.nim +++ b/src/tiny_sqlite/sqlite_wrapper.nim @@ -21,7 +21,7 @@ type {.cdecl, raises: [].} SqliteDestructor* = proc (p: pointer) - {.cdecl, locks: 0, tags: [], raises: [], gcsafe.} + {.cdecl, tags: [], raises: [], gcsafe.} const SQLITE_OK* = 0.cint @@ -96,7 +96,7 @@ const SQLITE_ALTER_TABLE* = 26.cint SQLITE_REINDEX* = 27.cint SQLITE_DENY* = 1.cint - SQLITE_IGNORE* = 2.cint + SQLITE_IGNORE* = 2.cint SQLITE_DETERMINISTIC* = 0x800.cint const @@ -291,4 +291,4 @@ proc free*(z: cstring) when not defined(macosx): proc load_extension*(db: Sqlite3, filename: cstring, entry: cstring, error: var cstring): cint - {.cdecl, dynlib: Lib, importc: "sqlite3_load_extension".} \ No newline at end of file + {.cdecl, dynlib: Lib, importc: "sqlite3_load_extension".}