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. - Change `PaginationParams#orderBy` to accept a `seq[string]` to allow for sorting on multiple fields.
This commit is contained in:
@ -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)
|
||||
## try: stmt
|
||||
## finally: close(conn)
|
||||
## 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
|
||||
@ -306,7 +308,7 @@ type
|
||||
PaginationParams* = object
|
||||
pageSize*: int
|
||||
offset*: int
|
||||
orderBy*: Option[string]
|
||||
orderBy*: Option[seq[string]]
|
||||
|
||||
PagedRecords*[T] = object
|
||||
pagination*: Option[PaginationParams]
|
||||
@ -438,7 +440,8 @@ template findRecordsWhere*[D: DbConnType](
|
||||
if page.isSome:
|
||||
let p = page.get
|
||||
if p.orderBy.isSome:
|
||||
fetchStmt &= " ORDER BY " & identNameToDb(p.orderBy.get)
|
||||
let orderByClause = page.orderBy.get.map(identNameToDb).join(",")
|
||||
fetchStmt &= " ORDER BY " & orderByClause
|
||||
else:
|
||||
fetchStmt &= " ORDER BY id"
|
||||
|
||||
@ -469,7 +472,8 @@ template getAllRecords*[D: DbConnType](
|
||||
if page.isSome:
|
||||
let p = page.get
|
||||
if p.orderBy.isSome:
|
||||
fetchStmt &= " ORDER BY " & identNameToDb(p.orderBy.get)
|
||||
let orderByClause = page.orderBy.get.map(identNameToDb).join(",")
|
||||
fetchStmt &= " ORDER BY " & orderByClause
|
||||
else:
|
||||
fetchStmt &= " ORDER BY id"
|
||||
|
||||
@ -508,7 +512,8 @@ template findRecordsBy*[D: DbConnType](
|
||||
if page.isSome:
|
||||
let p = page.get
|
||||
if p.orderBy.isSome:
|
||||
fetchStmt &= " ORDER BY " & identNameToDb(p.orderBy.get)
|
||||
let orderByClause = page.orderBy.get.map(identNameToDb).join(",")
|
||||
fetchStmt &= " ORDER BY " & orderByClause
|
||||
else:
|
||||
fetchStmt &= " ORDER BY id"
|
||||
|
||||
@ -543,7 +548,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 +561,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 +571,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 +622,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 +646,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 +664,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 +716,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
|
||||
|
Reference in New Issue
Block a user