6 Commits
0.3.0 ... 0.3.4

Author SHA1 Message Date
279d9aa7fd Expose a number of useful utility methods and macros. 2021-08-02 05:54:56 -05:00
d90372127b Further fix for ISO8601 date parsing.
Recognize versions of timestamps with 'T' as the date/time separator.
For example, compare:

    '2021-08-01 23:14:00-05:00'
    '2021-08-01T23:14:00-05:00'

This commit adds support for the second flavor (and it's variations).
2021-08-01 23:14:18 -05:00
2b78727356 Fix for PostgreSQL timestamp with timezone fields.
The previous fix for PostgreSQL timestamp fields matched fields with and
without timezones, but did not properly parse values from fields that
included the timezone. Now we check for the presence of the timezone in
the date string and choose a format string to parse it correctly.
2021-07-05 11:24:06 -05:00
445c86f97e Shuffle variable declarations to make functions GC-safe. 2021-07-02 20:34:29 -05:00
126167fdaf Fix name mapping in field lookups generation. 2021-07-02 20:16:49 -05:00
ff0c5e5305 Update to support Nim 1.4.x+ 2021-04-02 13:58:08 -05:00
3 changed files with 41 additions and 25 deletions

View File

@ -1,6 +1,6 @@
# Package # Package
version = "0.3.0" version = "0.3.4"
author = "Jonathan Bernard" author = "Jonathan Bernard"
description = "Lightweight Postgres ORM for Nim." description = "Lightweight Postgres ORM for Nim."
license = "GPL-3.0" license = "GPL-3.0"
@ -10,4 +10,4 @@ srcDir = "src"
# Dependencies # Dependencies
requires "nim >= 1.0.4" requires "nim >= 1.4.0"

View File

@ -3,6 +3,14 @@ import db_postgres, macros, options, sequtils, strutils, uuids
from unicode import capitalize from unicode import capitalize
import ./fiber_orm/util import ./fiber_orm/util
export
util.columnNamesForModel,
util.dbFormat,
util.dbNameToIdent,
util.identNameToDb,
util.modelName,
util.rowToModel,
util.tableName
type NotFoundError* = object of CatchableError type NotFoundError* = object of CatchableError
@ -25,12 +33,12 @@ proc createRecord*[T](db: DbConn, rec: T): T =
" RETURNING *"), mc.values) " RETURNING *"), mc.values)
result = rowToModel(T, newRow) result = rowToModel(T, newRow)
proc updateRecord*[T](db: DbConn, rec: T): bool = proc updateRecord*[T](db: DbConn, rec: T): bool =
var mc = newMutateClauses() var mc = newMutateClauses()
populateMutateClauses(rec, false, mc) populateMutateClauses(rec, false, mc)
let setClause = zip(mc.columns, mc.placeholders).mapIt(it.a & " = " & it.b).join(",") let setClause = zip(mc.columns, mc.placeholders).mapIt(it[0] & " = " & it[1]).join(",")
let numRowsUpdated = db.execAffectedRows(sql( let numRowsUpdated = db.execAffectedRows(sql(
"UPDATE " & tableName(rec) & "UPDATE " & tableName(rec) &
" SET " & setClause & " SET " & setClause &
@ -139,7 +147,7 @@ macro generateProcsForFieldLookups*(dbType: type, modelsAndFields: openarray[tup
# Add dynamic parameters for the proc definition and inner proc call # Add dynamic parameters for the proc definition and inner proc call
for n in fieldNames: for n in fieldNames:
let paramTuple = newNimNode(nnkPar) let paramTuple = newNimNode(nnkPar)
paramTuple.add(newColonExpr(ident("field"), newLit(n))) paramTuple.add(newColonExpr(ident("field"), newLit(identNameToDb(n))))
paramTuple.add(newColonExpr(ident("value"), ident(n))) paramTuple.add(newColonExpr(ident("value"), ident(n)))
procDefAST[3].add(newIdentDefs(ident(n), ident("string"))) procDefAST[3].add(newIdentDefs(ident(n), ident("string")))

View File

@ -3,14 +3,6 @@ import json, macros, options, sequtils, strutils, times, timeutils, unicode,
import nre except toSeq import nre except toSeq
const UNDERSCORE_RUNE = "_".toRunes[0]
const PG_TIMESTAMP_FORMATS = [
"yyyy-MM-dd HH:mm:sszz",
"yyyy-MM-dd HH:mm:ss'.'fffzz"
]
var PG_PARTIAL_FORMAT_REGEX = re"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.)(\d{1,3})(\S+)?"
type type
MutateClauses* = object MutateClauses* = object
columns*: seq[string] columns*: seq[string]
@ -30,7 +22,9 @@ macro modelName*(model: object): string =
macro modelName*(modelType: type): string = macro modelName*(modelType: type): string =
return newStrLitNode($modelType.getType[1]) return newStrLitNode($modelType.getType[1])
proc identNameToDb*(name: string): string = proc identNameToDb*(name: string): string =
const UNDERSCORE_RUNE = "_".toRunes[0]
let nameInRunes = name.toRunes let nameInRunes = name.toRunes
var prev: Rune var prev: Rune
var resultRunes = newSeq[Rune]() var resultRunes = newSeq[Rune]()
@ -70,27 +64,41 @@ type DbArrayParseState = enum
expectStart, inQuote, inVal, expectEnd expectStart, inQuote, inVal, expectEnd
proc parsePGDatetime*(val: string): DateTime = proc parsePGDatetime*(val: string): DateTime =
var errStr = ""
# Try to parse directly using known format strings. const PG_TIMESTAMP_FORMATS = [
for df in PG_TIMESTAMP_FORMATS: "yyyy-MM-dd HH:mm:ss",
try: return val.parse(df) "yyyy-MM-dd'T'HH:mm:ss",
except: errStr &= "\n\t" & getCurrentExceptionMsg() "yyyy-MM-dd HH:mm:sszz",
"yyyy-MM-dd'T'HH:mm:sszz",
"yyyy-MM-dd HH:mm:ss'.'fff",
"yyyy-MM-dd'T'HH:mm:ss'.'fff",
"yyyy-MM-dd HH:mm:ss'.'fffzz",
"yyyy-MM-dd'T'HH:mm:ss'.'fffzz",
"yyyy-MM-dd HH:mm:ss'.'fffzzz",
"yyyy-MM-dd'T'HH:mm:ss'.'fffzzz",
]
var correctedVal = val;
# PostgreSQL will truncate any trailing 0's in the millisecond value leading # PostgreSQL will truncate any trailing 0's in the millisecond value leading
# to values like `2020-01-01 16:42.3+00`. This cannot currently be parsed by # to values like `2020-01-01 16:42.3+00`. This cannot currently be parsed by
# the standard times format as it expects exactly three digits for # the standard times format as it expects exactly three digits for
# millisecond values. So we have to detect this and pad out the millisecond # millisecond values. So we have to detect this and pad out the millisecond
# value to 3 digits. # value to 3 digits.
let PG_PARTIAL_FORMAT_REGEX = re"(\d{4}-\d{2}-\d{2}( |'T')\d{2}:\d{2}:\d{2}\.)(\d{1,2})(\S+)?"
let match = val.match(PG_PARTIAL_FORMAT_REGEX) let match = val.match(PG_PARTIAL_FORMAT_REGEX)
if match.isSome: if match.isSome:
let c = match.get.captures let c = match.get.captures
try: if c.toSeq.len == 2: correctedVal = c[0] & alignLeft(c[2], 3, '0')
let corrected = c[0] & alignLeft(c[1], 3, '0') & c[2] else: correctedVal = c[0] & alignLeft(c[2], 3, '0') & c[3]
return corrected.parse(PG_TIMESTAMP_FORMATS[1])
except: var errStr = ""
errStr &= "\n\t" & PG_TIMESTAMP_FORMATS[1] &
" after padding out milliseconds to full 3-digits" # Try to parse directly using known format strings.
for df in PG_TIMESTAMP_FORMATS:
try: return correctedVal.parse(df)
except: errStr &= "\n\t" & getCurrentExceptionMsg()
raise newException(ValueError, "Cannot parse PG date. Tried:" & errStr) raise newException(ValueError, "Cannot parse PG date. Tried:" & errStr)
@ -278,7 +286,7 @@ proc typeOfColumn*(modelType: NimNode, colName: string): NimNode =
proc isEmpty(val: int): bool = return val == 0 proc isEmpty(val: int): bool = return val == 0
proc isEmpty(val: UUID): bool = return val.isZero proc isEmpty(val: UUID): bool = return val.isZero
proc isEmpty(val: string): bool = return val.isNilOrWhitespace proc isEmpty(val: string): bool = return val.isEmptyOrWhitespace
macro populateMutateClauses*(t: typed, newRecord: bool, mc: var MutateClauses): untyped = macro populateMutateClauses*(t: typed, newRecord: bool, mc: var MutateClauses): untyped =