Support Nim 2.x, compatibility with waterpark.
- Nim 2.x has moved the DB connectors outside the standard library to the `db_connector` package. - Refactor the pooling implementation and macro expectations to use the `withConnection` name instead of `withConn`. This change allows a caller to use a [waterpark](https://github.com/guzba/waterpark) pool instance instead of the builtin pool instance. Waterpark provides better support for multi-threaded environments. The builtin pooling mechanism may be deprecated in favor of waterpark in the future. - Add the `getModelIfItExists` generated proc to the list of standard procs we generate. This is a flavour of `getModel` that returns an `Option` instead of raising an exception when there is no model for the given id.
This commit is contained in:
parent
fb74d84cb7
commit
9046925269
@ -57,7 +57,7 @@ Models may be defined as:
|
||||
|
||||
.. code-block:: Nim
|
||||
# models.nim
|
||||
import std/options, std/times
|
||||
import std/[options, times]
|
||||
import uuids
|
||||
|
||||
type
|
||||
@ -82,6 +82,8 @@ Using Fiber ORM we can generate a data access layer with:
|
||||
|
||||
.. code-block:: Nim
|
||||
# db.nim
|
||||
import std/[options]
|
||||
import db_connectors/db_postgres
|
||||
import fiber_orm
|
||||
import ./models.nim
|
||||
|
||||
@ -102,6 +104,7 @@ This will generate the following procedures:
|
||||
|
||||
.. code-block:: Nim
|
||||
proc getTodoItem*(db: TodoDB, id: UUID): TodoItem;
|
||||
proc getTodoItemIfItExists*(db: TodoDB, id: UUID): Option[TodoItem];
|
||||
proc getAllTodoItems*(db: TodoDB): seq[TodoItem];
|
||||
proc createTodoItem*(db: TodoDB, rec: TodoItem): TodoItem;
|
||||
proc updateTodoItem*(db: TodoDB, rec: TodoItem): bool;
|
||||
@ -112,6 +115,7 @@ This will generate the following procedures:
|
||||
values: varargs[string, dbFormat]): seq[TodoItem];
|
||||
|
||||
proc getTimeEntry*(db: TodoDB, id: UUID): TimeEntry;
|
||||
proc getTimeEntryIfItExists*(db: TodoDB, id: UUID): Option[TimeEntry];
|
||||
proc getAllTimeEntries*(db: TodoDB): seq[TimeEntry];
|
||||
proc createTimeEntry*(db: TodoDB, rec: TimeEntry): TimeEntry;
|
||||
proc updateTimeEntry*(db: TodoDB, rec: TimeEntry): bool;
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Package
|
||||
|
||||
version = "2.2.0"
|
||||
version = "3.0.0"
|
||||
author = "Jonathan Bernard"
|
||||
description = "Lightweight Postgres ORM for Nim."
|
||||
license = "GPL-3.0"
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Fiber ORM
|
||||
#
|
||||
# Copyright 2019 Jonathan Bernard <jonathan@jdbernard.com>
|
||||
# Copyright 2019-2024 Jonathan Bernard <jonathan@jdbernard.com>
|
||||
|
||||
## Lightweight ORM supporting the `Postgres`_ and `SQLite`_ databases in Nim.
|
||||
## It supports a simple, opinionated model mapper to generate SQL queries based
|
||||
@ -264,26 +264,28 @@
|
||||
## In the example above the `pool.DbConnPool`_ object is used as database
|
||||
## object type (aliased as `TodoDB`). This is the intended usage pattern, but
|
||||
## anything can be passed as the database object type so long as there is a
|
||||
## defined `withConn` template that provides an injected `conn: DbConn` object
|
||||
## defined `withConnection` template that provides a `conn: DbConn` object
|
||||
## to the provided statement body.
|
||||
##
|
||||
## For example, a valid database object implementation that opens a new
|
||||
## connection for every request might look like this:
|
||||
##
|
||||
## .. code-block:: Nim
|
||||
## import std/db_postgres
|
||||
## import db_connector/db_postgres
|
||||
##
|
||||
## type TodoDB* = object
|
||||
## connString: string
|
||||
##
|
||||
## template withConn*(db: TodoDB, stmt: untyped): untyped =
|
||||
## let conn {.inject.} = open("", "", "", db.connString)
|
||||
## template withConnection*(db: TodoDB, stmt: untyped): untyped =
|
||||
## block:
|
||||
## let conn = open("", "", "", db.connString)
|
||||
## try: stmt
|
||||
## finally: close(conn)
|
||||
##
|
||||
## .. _pool.DbConnPool: fiber_orm/pool.html#DbConnPool
|
||||
##
|
||||
import std/[db_common, logging, macros, options, sequtils, strutils]
|
||||
import std/[logging, macros, options, sequtils, strutils]
|
||||
import db_connector/db_common
|
||||
import namespaced_logging, uuids
|
||||
|
||||
from std/unicode import capitalize
|
||||
@ -543,7 +545,7 @@ macro generateProcsForModels*(dbType: type, modelTypes: openarray[type]): untype
|
||||
## proc findTodoItemsWhere*(
|
||||
## db: TodoDB, whereClause: string, values: varargs[string]): TodoItem;
|
||||
##
|
||||
## `dbType` is expected to be some type that has a defined `withConn`
|
||||
## `dbType` is expected to be some type that has a defined `withConnection`
|
||||
## procedure (see `Database Object`_ for details).
|
||||
##
|
||||
## .. _Database Object: #database-object
|
||||
@ -556,6 +558,7 @@ macro generateProcsForModels*(dbType: type, modelTypes: openarray[type]): untype
|
||||
|
||||
let modelName = $(t.getType[1])
|
||||
let getName = ident("get" & modelName)
|
||||
let getIfExistsName = ident("get" & modelName & "IfItExists")
|
||||
let getAllName = ident("getAll" & pluralize(modelName))
|
||||
let findWhereName = ident("find" & pluralize(modelName) & "Where")
|
||||
let createName = ident("create" & modelName)
|
||||
@ -565,33 +568,38 @@ macro generateProcsForModels*(dbType: type, modelTypes: openarray[type]): untype
|
||||
let idType = typeOfColumn(t, "id")
|
||||
result.add quote do:
|
||||
proc `getName`*(db: `dbType`, id: `idType`): `t` =
|
||||
db.withConn: result = getRecord(conn, `t`, id)
|
||||
db.withConnection conn: result = getRecord(conn, `t`, id)
|
||||
|
||||
proc `getIfExistsName`*(db: `dbType`, id: `idType`): Option[`t`] =
|
||||
db.withConnection conn:
|
||||
try: result = some(getRecord(conn, `t`, id))
|
||||
except NotFoundError: result = none[`t`]()
|
||||
|
||||
proc `getAllName`*(db: `dbType`, pagination = none[PaginationParams]()): PagedRecords[`t`] =
|
||||
db.withConn: result = getAllRecords(conn, `t`, pagination)
|
||||
db.withConnection conn: result = getAllRecords(conn, `t`, pagination)
|
||||
|
||||
proc `findWhereName`*(
|
||||
db: `dbType`,
|
||||
whereClause: string,
|
||||
values: varargs[string, dbFormat],
|
||||
pagination = none[PaginationParams]()): PagedRecords[`t`] =
|
||||
db.withConn:
|
||||
db.withConnection conn:
|
||||
result = findRecordsWhere(conn, `t`, whereClause, values, pagination)
|
||||
|
||||
proc `createName`*(db: `dbType`, rec: `t`): `t` =
|
||||
db.withConn: result = createRecord(conn, rec)
|
||||
db.withConnection conn: result = createRecord(conn, rec)
|
||||
|
||||
proc `updateName`*(db: `dbType`, rec: `t`): bool =
|
||||
db.withConn: result = updateRecord(conn, rec)
|
||||
db.withConnection conn: result = updateRecord(conn, rec)
|
||||
|
||||
proc `createOrUpdateName`*(db: `dbType`, rec: `t`): `t` =
|
||||
db.inTransaction: result = createOrUpdateRecord(conn, rec)
|
||||
|
||||
proc `deleteName`*(db: `dbType`, rec: `t`): bool =
|
||||
db.withConn: result = deleteRecord(conn, rec)
|
||||
db.withConnection conn: result = deleteRecord(conn, rec)
|
||||
|
||||
proc `deleteName`*(db: `dbType`, id: `idType`): bool =
|
||||
db.withConn: result = deleteRecord(conn, `t`, id)
|
||||
db.withConnection conn: result = deleteRecord(conn, `t`, id)
|
||||
|
||||
macro generateLookup*(dbType: type, modelType: type, fields: seq[string]): untyped =
|
||||
## Create a lookup procedure for a given set of field names. For example,
|
||||
@ -611,7 +619,7 @@ macro generateLookup*(dbType: type, modelType: type, fields: seq[string]): untyp
|
||||
# Create proc skeleton
|
||||
result = quote do:
|
||||
proc `procName`*(db: `dbType`): PagedRecords[`modelType`] =
|
||||
db.withConn: result = findRecordsBy(conn, `modelType`)
|
||||
db.withConnection conn: result = findRecordsBy(conn, `modelType`)
|
||||
|
||||
var callParams = quote do: @[]
|
||||
|
||||
@ -635,11 +643,11 @@ macro generateLookup*(dbType: type, modelType: type, fields: seq[string]): untyp
|
||||
|
||||
# Add the call params to the inner procedure call
|
||||
# result[6][0][1][0][1] is
|
||||
# ProcDef -> [6]: StmtList (body) -> [0]: Call ->
|
||||
# [1]: StmtList (withConn body) -> [0]: Asgn (result =) ->
|
||||
# ProcDef -> [6]: StmtList (body) -> [0]: Command ->
|
||||
# [2]: StmtList (withConnection body) -> [0]: Asgn (result =) ->
|
||||
# [1]: Call (inner findRecords invocation)
|
||||
result[6][0][1][0][1].add(callParams)
|
||||
result[6][0][1][0][1].add(quote do: pagination)
|
||||
result[6][0][2][0][1].add(callParams)
|
||||
result[6][0][2][0][1].add(quote do: pagination)
|
||||
|
||||
macro generateProcsForFieldLookups*(dbType: type, modelsAndFields: openarray[tuple[t: type, fields: seq[string]]]): untyped =
|
||||
result = newStmtList()
|
||||
@ -653,7 +661,7 @@ macro generateProcsForFieldLookups*(dbType: type, modelsAndFields: openarray[tup
|
||||
# Create proc skeleton
|
||||
let procDefAST = quote do:
|
||||
proc `procName`*(db: `dbType`): PagedRecords[`modelType`] =
|
||||
db.withConn: result = findRecordsBy(conn, `modelType`)
|
||||
db.withConnection conn: result = findRecordsBy(conn, `modelType`)
|
||||
|
||||
var callParams = quote do: @[]
|
||||
|
||||
@ -705,8 +713,8 @@ proc initPool*[D: DbConnType](
|
||||
hardCap: hardCap,
|
||||
healthCheckQuery: healthCheckQuery))
|
||||
|
||||
template inTransaction*[D: DbConnType](db: DbConnPool[D], body: untyped) =
|
||||
pool.withConn(db):
|
||||
template inTransaction*(db, body: untyped) =
|
||||
db.withConnection conn:
|
||||
conn.exec(sql"BEGIN TRANSACTION")
|
||||
try:
|
||||
body
|
||||
|
@ -1,3 +1,3 @@
|
||||
import std/[db_postgres, db_sqlite]
|
||||
import db_connector/[db_postgres, db_sqlite]
|
||||
|
||||
type DbConnType* = db_postgres.DbConn or db_sqlite.DbConn
|
||||
|
@ -4,10 +4,11 @@
|
||||
|
||||
## Simple database connection pooling implementation compatible with Fiber ORM.
|
||||
|
||||
import std/[db_common, logging, sequtils, strutils, sugar]
|
||||
import std/[logging, sequtils, strutils, sugar]
|
||||
import db_connector/db_common
|
||||
|
||||
from std/db_sqlite import getRow, close
|
||||
from std/db_postgres import getRow, close
|
||||
from db_connector/db_sqlite import getRow, close
|
||||
from db_connector/db_postgres import getRow, close
|
||||
|
||||
import namespaced_logging
|
||||
import ./db_common as fiber_db_common
|
||||
@ -58,6 +59,7 @@ proc initDbConnPool*[D: DbConnType](cfg: DbConnPoolConfig[D]): DbConnPool[D] =
|
||||
proc newConn[D: DbConnType](pool: DbConnPool[D]): PooledDbConn[D] =
|
||||
log().debug("Creating a new connection to add to the pool.")
|
||||
pool.lastId += 1
|
||||
{.gcsafe.}:
|
||||
let conn = pool.cfg.connect()
|
||||
result = PooledDbConn[D](
|
||||
conn: conn,
|
||||
@ -124,12 +126,10 @@ proc release*[D: DbConnType](pool: DbConnPool[D], connId: int): void =
|
||||
let foundConn = pool.conns.filterIt(it.id == connId)
|
||||
if foundConn.len > 0: foundConn[0].free = true
|
||||
|
||||
template withConn*[D: DbConnType](pool: DbConnPool[D], stmt: untyped): untyped =
|
||||
template withConnection*[D: DbConnType](pool: DbConnPool[D], conn, stmt: untyped): untyped =
|
||||
## Convenience template to provide a connection from the pool for use in a
|
||||
## statement block, automatically releasing that connnection when done.
|
||||
##
|
||||
## The provided connection is injected as the variable `conn` in the
|
||||
## statement body.
|
||||
let (connId, conn {.inject.}) = take(pool)
|
||||
block:
|
||||
let (connId, conn) = take(pool)
|
||||
try: stmt
|
||||
finally: release(pool, connId)
|
||||
|
Loading…
x
Reference in New Issue
Block a user