Initial stab at better documentation.
This commit is contained in:
@ -1,3 +1,38 @@
|
||||
# Fiber ORM
|
||||
#
|
||||
# Copyright 2019 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
|
||||
## on Nim objects. It also includes a simple connection pooling implementation.
|
||||
##
|
||||
## .. _Postgres: https://nim-lang.org/docs/db_postgres.html
|
||||
## .. _SQLite: https://nim-lang.org/docs/db_sqlite.html
|
||||
##
|
||||
## Basic Usage
|
||||
## ===========
|
||||
##
|
||||
## Object-Relational Modeling
|
||||
## ==========================
|
||||
##
|
||||
## Model Class
|
||||
## -----------
|
||||
##
|
||||
## Table Name
|
||||
## ``````````
|
||||
##
|
||||
## Column Names
|
||||
## ````````````
|
||||
##
|
||||
## ID Field
|
||||
## ````````
|
||||
##
|
||||
## Supported Data Types
|
||||
## --------------------
|
||||
##
|
||||
## Database Object
|
||||
## ===============
|
||||
|
||||
import std/db_postgres, std/macros, std/options, std/sequtils, std/strutils
|
||||
import namespaced_logging, uuids
|
||||
|
||||
@ -16,7 +51,8 @@ export
|
||||
util.rowToModel,
|
||||
util.tableName
|
||||
|
||||
type NotFoundError* = object of CatchableError
|
||||
type NotFoundError* = object of CatchableError ##\
|
||||
## Error type raised when no record matches a given ID
|
||||
|
||||
var logNs {.threadvar.}: LoggingNamespace
|
||||
|
||||
@ -31,6 +67,15 @@ proc newMutateClauses(): MutateClauses =
|
||||
values: @[])
|
||||
|
||||
proc createRecord*[T](db: DbConn, rec: T): T =
|
||||
## Create a new record. `rec` is expected to be a `model class`_. The `id
|
||||
## field`_ is only set if it is `non-empty`_
|
||||
##
|
||||
## Returns the newly created record.
|
||||
##
|
||||
## .. _model class: #objectminusrelational-modeling-model-class
|
||||
## .. _id field: #model-class-id-field
|
||||
## .. _non-empty:
|
||||
|
||||
var mc = newMutateClauses()
|
||||
populateMutateClauses(rec, true, mc)
|
||||
|
||||
@ -46,6 +91,9 @@ proc createRecord*[T](db: DbConn, rec: T): T =
|
||||
result = rowToModel(T, newRow)
|
||||
|
||||
proc updateRecord*[T](db: DbConn, rec: T): bool =
|
||||
## Update a record by id. `rec` is expected to be a `model class`_.
|
||||
##
|
||||
## .. _model class: #objectminusrelational-modeling-model-class
|
||||
var mc = newMutateClauses()
|
||||
populateMutateClauses(rec, false, mc)
|
||||
|
||||
@ -61,16 +109,21 @@ proc updateRecord*[T](db: DbConn, rec: T): bool =
|
||||
return numRowsUpdated > 0;
|
||||
|
||||
template deleteRecord*(db: DbConn, modelType: type, id: typed): untyped =
|
||||
## Delete a record by id.
|
||||
let sqlStmt = "DELETE FROM " & tableName(modelType) & " WHERE id = ?"
|
||||
log().debug "deleteRecord: [" & sqlStmt & "] id: " & $id
|
||||
db.tryExec(sql(sqlStmt), $id)
|
||||
|
||||
proc deleteRecord*[T](db: DbConn, rec: T): bool =
|
||||
## Delete a record by `id`_.
|
||||
##
|
||||
## .. _id: #model-class-id-field
|
||||
let sqlStmt = "DELETE FROM " & tableName(rec) & " WHERE id = ?"
|
||||
log().debug "deleteRecord: [" & sqlStmt & "] id: " & $rec.id
|
||||
return db.tryExec(sql(sqlStmt), $rec.id)
|
||||
|
||||
template getRecord*(db: DbConn, modelType: type, id: typed): untyped =
|
||||
## Fetch a record by id.
|
||||
let sqlStmt =
|
||||
"SELECT " & columnNamesForModel(modelType).join(",") &
|
||||
" FROM " & tableName(modelType) &
|
||||
@ -85,6 +138,9 @@ template getRecord*(db: DbConn, modelType: type, id: typed): untyped =
|
||||
rowToModel(modelType, row)
|
||||
|
||||
template findRecordsWhere*(db: DbConn, modelType: type, whereClause: string, values: varargs[string, dbFormat]): untyped =
|
||||
## Find all records matching a given `WHERE` clause. The number of elements in
|
||||
## the `values` array must match the number of placeholders (`?`) in the
|
||||
## provided `WHERE` clause.
|
||||
let sqlStmt =
|
||||
"SELECT " & columnNamesForModel(modelType).join(",") &
|
||||
" FROM " & tableName(modelType) &
|
||||
@ -94,6 +150,7 @@ template findRecordsWhere*(db: DbConn, modelType: type, whereClause: string, val
|
||||
db.getAllRows(sql(sqlStmt), values).mapIt(rowToModel(modelType, it))
|
||||
|
||||
template getAllRecords*(db: DbConn, modelType: type): untyped =
|
||||
## Fetch all records of the given type.
|
||||
let sqlStmt =
|
||||
"SELECT " & columnNamesForModel(modelType).join(",") &
|
||||
" FROM " & tableName(modelType)
|
||||
@ -102,6 +159,7 @@ template getAllRecords*(db: DbConn, modelType: type): untyped =
|
||||
db.getAllRows(sql(sqlStmt)).mapIt(rowToModel(modelType, it))
|
||||
|
||||
template findRecordsBy*(db: DbConn, modelType: type, lookups: seq[tuple[field: string, value: string]]): untyped =
|
||||
## Find all records matching the provided lookup values.
|
||||
let sqlStmt =
|
||||
"SELECT " & columnNamesForModel(modelType).join(",") &
|
||||
" FROM " & tableName(modelType) &
|
||||
@ -112,6 +170,23 @@ template findRecordsBy*(db: DbConn, modelType: type, lookups: seq[tuple[field: s
|
||||
db.getAllRows(sql(sqlStmt), values).mapIt(rowToModel(modelType, it))
|
||||
|
||||
macro generateProcsForModels*(dbType: type, modelTypes: openarray[type]): untyped =
|
||||
## Generate all standard access procedures for the given model types. For a
|
||||
## `model class`_ named `SampleRecord`, this will generate the following
|
||||
## procedures:
|
||||
##
|
||||
## .. code-block:: Nim
|
||||
## proc getSampleRecord*(db: dbType): SampleRecord;
|
||||
## proc getAllSampleRecords*(db: dbType): SampleRecord;
|
||||
## proc createSampleRecord*(db: dbType, rec: SampleRecord): SampleRecord;
|
||||
## proc deleteSampleRecord*(db: dbType, rec: SampleRecord): bool;
|
||||
## proc deleteSampleRecord*(db: dbType, id: idType): bool;
|
||||
## proc updateSampleRecord*(db: dbType, rec: SampleRecord): bool;
|
||||
##
|
||||
## proc findSampleRecordsWhere*(
|
||||
## db: dbType, whereClause: string, values: varargs[string]): SampleRecord;
|
||||
##
|
||||
## `dbType` is expected to be some type that has a defined `withConn`_
|
||||
## procedure.
|
||||
result = newStmtList()
|
||||
|
||||
for t in modelTypes:
|
||||
@ -147,6 +222,15 @@ macro generateProcsForModels*(dbType: type, modelTypes: openarray[type]): untype
|
||||
db.withConn: 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,
|
||||
##
|
||||
## .. code-block:: Nim
|
||||
## generateLookup(SampleDB, SampleRecord, ["name", "location"])
|
||||
##
|
||||
## will generate the following procedure:
|
||||
##
|
||||
## .. code-block:: Nim
|
||||
## proc findSampleRecordsByNameAndLocation*(db: SampleDB,
|
||||
let fieldNames = fields[1].mapIt($it)
|
||||
let procName = ident("find" & pluralize($modelType.getType[1]) & "By" & fieldNames.mapIt(it.capitalize).join("And"))
|
||||
|
||||
@ -211,6 +295,20 @@ proc initPool*(
|
||||
poolSize = 10,
|
||||
hardCap = false,
|
||||
healthCheckQuery = "SELECT 'true' AS alive"): DbConnPool =
|
||||
## Initialize a new DbConnPool.
|
||||
##
|
||||
## * `connect` must be a factory which creates a new `DbConn`
|
||||
## * `poolSize` sets the desired capacity of the connection pool.
|
||||
## * `hardCap` defaults to `false`.
|
||||
##
|
||||
## When `false`, the pool can grow beyond the configured capacity, but will
|
||||
## release connections down to the its capacity (no less than `poolSize`).
|
||||
##
|
||||
## When `true` the pool will not create more than its configured capacity.
|
||||
## It a connection is requested, none are free, and the pool is at
|
||||
## capacity, this will result in an Error being raised.
|
||||
## * `healthCheckQuery` should be a simple and fast SQL query that the pool
|
||||
## can use to test the liveliness of pooled connections.
|
||||
|
||||
initDbConnPool(DbConnPoolConfig(
|
||||
connect: connect,
|
||||
|
Reference in New Issue
Block a user