Hello!
I've been wanting to write an ORM for Nim for several years, but always failed very early in the process of writing one. It turns out, simply defining what to implement and what to leave out of the scope is hard enough to ahve discouraged more than once.
However, I finally have something I'm not entire embarassed to share with the community: Norm.
Norm turns type sections into a DB schema and provides ready to use CRUD procs to query the data. Norm tries to be as thin as possible, so by default object fields are mapped directly to DB columnds. However, it allows you to provide custom converters from object to DB and vice versa, so you can store and retrieve complex Nim types as native DB types.
I'll do my best to write up a blog post about Norm and provide some examples in it, but I'm short on time at the moment, so here's a sample app that demonstrates a REST API created with Jester and Norm.
Feel free to share your questions and suggestions here, I'll try to respond to all messages.
Thanks!
Here's an example of a DB schema definition (from https://github.com/moigagoo/norm-sample-webapp/blob/develop/src/models.nim):
db("app.db", "", "", ""):
type
Owner* = object
firstName*: string
lastName*: string
birthDate* {.
dbType: "INTEGER",
parseIt: it.parseInt().fromUnix().utc(),
formatIt: $it.toTime().toUnix()
.}: DateTime
proc getOwnerById(id: string): Owner =
result = Owner(birthDate: now())
withDb:
result.getOne parseInt(id)
type
Pet* = object
name*: string
birthDate* {.
dbType: "INTEGER",
parseIt: it.parseInt().fromUnix().utc(),
formatIt: $it.toTime().toUnix()
.}: DateTime
owner* {.
dbCol: "ownerId",
dbType: "INTEGER",
fk: Owner
parser: getOwnerById,
formatIt: $it.id
.}: Owner
Thanks for the positive comment! It means a lot to me, for real.
Currently, there's no way to shorten or reuse field definition, and DateTime is considered a special case, so right now, unfortunately, it's birthDate {.dbType: "INTEGER", parseIt: it.parseInt().fromUnix().local(), formatIt: $it.toTime().toUnix().}: DateTime repeated multiple times in your DB schema. It's the only place you'll have to have this ugly code repetition though.
However, I've been thinking a lot about adding default parser and formatter for DateTime type. In SQLite, they'd be stored as INTEGER and in PostgreSQL as TIMESTAMP. Sounds like reasonable defaults.
DateTime is a common type and I can imagine developers rewriting the same parsers and formatters over and over. Do you think it's a good idea?
You can probably use custom pragmas:
{.pragma: dbTime, dbType: "INTEGER", parseIt: timeparser, formatIt: timeformatter.}
Are all the incoming types from the database strings?
I'm not sure I understand the question. Row a.k.a. seq[string] is converted into an object based on the types of the object's fields. If the fields are int, float, and string, then a row @["123", "123.321", "foo"] is converted into 123, 123.123, "foo".
AFAIK Nim db modules' rows are always sequences of string regardless of the underlying DB types. So I'm relying on the target object's types. You can see the code that does the conversions in rowutils module.
How does this handle NULL?
I haven't tested it with NULL values bth. I should though. How it's handled depends on how Nim's db modules handle NULL. I think it's returned as an empty string.
I just want to make it clear that Norm is my first attempt to ORM and that decisions I've made may be wrong. Any advice from seasoned developers is highly appreciated.
Also, I'd be happy to see Norm evolve into a usable tool, so input from its potential users is vital.
Hi @moigagoo
I've also had some trouble with NULL values. My question can be found in this thread: 4320
I ended using the library here nim_sqlbuilder, which substitute a type with = NULL.
@mashingan also provided a good solution using options in the forum thread.
There's also a github issue about the missing API to precompile the query and the bind the data later, which @mikra01 is working on.
... checked the range. Time is good to the year 292277026596 and presumably backwards that far.
So, you will have to worry about the Y292277026K problem.
It is my great pleasure to announce Norm version 1.1.0 release: https://moigagoo.github.io/norm/changelog.html
The biggest change addition are the procs to write migrations. With those in place, I can focus on writing a migration manager that will make Norm even more useful for real life usage.
Bonjour, Hello
887/5000
so if yes you can have a transactional connection and a logical connection only for reading ...
I work this way it allows me a lot of flexibility.
I worked on a wrapper (which works in C ++) based on Postgresql libpq.
I also did one with ECPG ... History of making a lib for each table containing access allowing to make the management a very functional test and a correct speed, this table in lib ... finally it was the materialization of fields and functional records identical to the AS400 DB2
I had thought of translating everything into nim, I stumbled on ECPG which demands absolutely "c" then converted to its sauce ... So I did not find it very nice for someone who uses nim ....
Bon voyage in your adventure
Did you think, open several connections > so if yes you can have a transactional connection and a logical connection only for reading ...
You can connect to several DBs with withDb and withCustomDb, which allows you to use one connection for reading and another for writing.
Did you think, open several connections
You mean connection pool? There's an open issue for it, but I'm not planning on taking on it any time soon. I plan to focus on the migration manager for Norm.
I'm proud to announce the first release of Norman, the migration manager for Norm: https://moigagoo.github.io/norman/norman.html
With Norman, you can keep your DB config separate from the models in a project config and generate, apply and undo migrations.
There are no tests at the moment, but they're on the way. Feel free to add Norman to your existing projects that use Norm, it should improve your developer experience.